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
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 theAuthorization header:
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:| Endpoint | Description |
|---|---|
GET /api/v1/notifications | User notification stream |
GET /api/v1/repos/{owner}/{repo}/runs/{id}/logs | Workflow run log stream |
GET /api/v1/repos/{owner}/{repo}/agent/sessions/{id}/stream | Agent session output stream |
Event Channels
Subscribe to specific channels using thechannels query parameter. Multiple channels can be specified as a comma-separated list.
channels parameter is provided, the server streams all events the authenticated user has access to.
Available Channels
| Channel | Description | Scope Required |
|---|---|---|
notification | User notifications (mentions, review requests, landing request updates) | read:notification |
agent.session | AI agent session updates (status changes, output messages) | read:repository |
workflow.log | Workflow run log streaming (step output, status changes) | read:repository |
landing_request.update | Landing request status changes (opened, reviewed, landed, conflicts) | read:repository |
repo.push | Repository push events (new commits pushed to bookmarks) | read:repository |
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 ofid, event, and data fields:
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 theLast-Event-ID header. If the connection drops, the client can resume where it left off by sending the ID of the last received event:
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::) 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:JavaScript
The browser-nativeEventSource API does not support custom headers. Use a polyfill like eventsource (Node.js) or @microsoft/fetch-event-source for environments that need token authentication:
eventsource package:
CLI
Thejjhub CLI uses SSE internally. The jjhub run watch command streams workflow logs in real time:
Error Handling
| Scenario | Behavior |
|---|---|
| Invalid or missing token | 401 Unauthorized JSON response, connection closed |
| Insufficient scope | 403 Forbidden JSON response, connection closed |
| Repository not found | 404 Not Found JSON response, connection closed |
| Server restart | Connection drops. Clients should reconnect with Last-Event-ID. |
| Client disconnect | Server detects the closed connection and cleans up resources |
| Internal error during stream | Server sends an error event, then closes the connection |
Backend: PostgreSQL LISTEN/NOTIFY
SSE endpoints are backed by PostgreSQL’sLISTEN/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.