Skip to main content
The realtime endpoint delivers live hub events to connected members over Server-Sent Events (SSE). Use it to drive badges, live discussion feeds, DM notifications, and online-presence indicators without polling.

Connect

The connection stays open indefinitely. The server sends events as they happen and a :heartbeat comment every 25 seconds to keep the TCP connection alive.

Error responses before the stream opens

Event frame format

Each event follows the standard SSE wire format:
The id field is a UUIDv7 string. Store it as Last-Event-ID for gap-free reconnects.

Event types

All emitted event types are registered at startup. Current set (updated with each release):

Community — spaces

Community — discussions

Community — comments

Payloads carry IDs only (for example comment_id, discussion_id, contact_id). Fetch the full resource from the REST API when you need body text or attributes.

Community — notifications

Use community.notification.created to update the unread badge in real time without polling GET /api/v1/hubs/{hub_id}/notifications/unread-count. The payload carries the notification’s id and hub_id; fetch the full notification resource if you need its category, kind, or preview text.

Community — profiles and DMs

Broadcasts

Test

Payload shape

Every event’s data is a JSON object. The exact fields vary by event type, but all payloads include identifiers (for example discussion_id, space_id, contact_id) so the client can look up the full resource if needed. Payloads intentionally carry IDs rather than full bodies to keep the event stream lean.

Access scoping

Events are delivered based on the member’s access at connect time:
  • Hub-scoped events (spaces, directory) reach all connected members.
  • Space-scoped events (discussions, reactions) are filtered: restricted spaces are only delivered to members whose segment grants access.
  • Direct-targeted events (DMs, 1:1 presence) are delivered only to the named contact IDs.
If a member’s access changes while they are connected (for example, they are removed from a restricted space), the server updates the connection’s access set without requiring a reconnect.

Terminal events

The server closes the stream by emitting a terminal event before dropping the TCP connection. Terminal events have no id field and are not resumable.

Reconnect and gap-free resume

Store the most recent id value from received events. On reconnect, send it as Last-Event-ID:
The server replays all events emitted after that ID from the last 5 minutes, then switches to live delivery. If the cursor is older than 5 minutes the server emits stream.stale_resume — on receiving that event, perform a full page refresh to reload state, then reconnect without a Last-Event-ID. Last-Event-ID must be a UUIDv7 string. Sending any other format returns 400.

Heartbeat

The server sends an SSE comment line every 25 seconds:
This keeps the TCP connection alive through proxies. No action is needed on the client; SSE clients ignore comment lines. If neither an event nor a heartbeat is received for 90 seconds the server closes the connection — reconnect using the standard Last-Event-ID flow.

Connection limit

A single contact may hold at most 5 concurrent SSE connections across all hubs. Opening a sixth connection returns 429. Close unused connections (for example, when navigating away from a hub view) to stay within this limit.

X-API-Version

Every SSE response (including the initial HTTP 200 that establishes the stream) includes the X-API-Version response header set to the running build version. See X-API-Version below.

X-API-Version header

Every response from the API — including SSE streams, webhooks, and preflight CORS responses — includes:
The value is the build version of the currently running instance (same as the version field from GET /version). Use it to:
  • Confirm a deploy has completed and the expected version is live.
  • Emit version metadata in client telemetry.
  • Debug which build served a specific request when the X-Request-ID alone is not enough.
The header is set on the outermost middleware layer, so it is present even on responses that short-circuit before reaching any route handler (for example, CORS preflight responses). You do not need to send X-API-Version on requests — it is a response-only header.