Skip to main content

Server-Sent Events (SSE)

JJHub uses Server-Sent Events (SSE) for all real-time features. SSE provides a lightweight, HTTP-based protocol for streaming events from the server to connected clients. JJHub chose SSE over WebSockets or polling because it works naturally with HTTP infrastructure, supports automatic reconnection, and is well-suited for streaming use cases like workflow logs and agent output.

Endpoint

Connect to the unified event stream. The server responds with Content-Type: text/event-stream and holds the connection open, pushing events as they occur.

Authentication

SSE endpoints require a valid bearer token. Pass it in the Authorization header:
If the token is missing or invalid, the server responds with 401 Unauthorized and closes the connection. If the token lacks the required scope for the requested channels, the server responds with 403 Forbidden.

Resource-Specific SSE Endpoints

In addition to the unified stream, JJHub provides resource-specific SSE endpoints for targeted use cases: These endpoints follow the same SSE protocol, authentication, and reconnection behavior described below.

Event Channels

Subscribe to specific channels using the channels query parameter. Multiple channels can be specified as a comma-separated list.
If no channels parameter is provided, the server streams all events the authenticated user has access to.

Available Channels

Filtering by Repository

For repository-scoped channels (agent.session, workflow.log, landing_request.update, repo.push), filter events to a specific repository with the repo query parameter:

Event Format

Events follow the SSE specification. Each event consists of id, event, and data fields:
The data field is always a JSON object. The id field is a monotonically increasing integer that uniquely identifies each event in the stream.

Reconnection

SSE supports automatic reconnection through the Last-Event-ID header. If the connection drops, the client can resume where it left off by sending the ID of the last received event:
The server replays any events that occurred after the specified ID, then continues streaming new events. If the requested ID is too old (events are retained for a limited window), the server begins streaming from the current position and includes a missed_events warning:

Keep-Alive

The server sends a keep-alive comment every 15 seconds to prevent proxies, load balancers, and clients from closing idle connections:
SSE comments (lines starting with :) are ignored by conforming clients and do not trigger event handlers.

Timeout Handling

SSE endpoints are exempt from the 30-second HTTP timeout middleware that applies to regular API requests. SSE connections are long-lived by design and remain open until the client disconnects, the server restarts, or an error occurs.

Code Examples

curl

Stream all events the authenticated user has access to:
Stream workflow logs for a specific run:

JavaScript

The browser-native EventSource API does not support custom headers. Use a polyfill like eventsource (Node.js) or @microsoft/fetch-event-source for environments that need token authentication:
For Node.js with the eventsource package:

CLI

The jjhub CLI uses SSE internally. The jjhub run logs command (formerly watch) streams workflow logs in real time:

Error Handling

An error event during an active stream looks like:

Backend: PostgreSQL LISTEN/NOTIFY

SSE endpoints are backed by PostgreSQL’s LISTEN/NOTIFY mechanism for efficient event delivery. When a write occurs (a new workflow log line, a landing request status change, a notification), the service issues a NOTIFY on the relevant channel. SSE handlers LISTEN on those channels and push events to connected clients immediately, with no database polling. This architecture means events are delivered with minimal latency — typically within milliseconds of the underlying write.