Skip to main content
Marfa emits live events on a per-tenant Server-Sent Events stream. Clients that need to reflect changes in real time subscribe to GET /events.

Connecting

The connection stays open. The server streams events as they occur, each on its own SSE record:
Comments (lines starting with :) are heartbeats. The server sends one every ~30 seconds. Treat an idle stream longer than 60s as disconnected and reconnect.

Event types

Payloads are filtered by the subscriber’s permissions — the server won’t stream events for items or edges the credential can’t read.

Filtering

Clients can subscribe to a subset of events:
Filters apply on the server; unwanted events never leave the stream.

Reconnecting with Last-Event-ID

On reconnect, the client sends the Last-Event-ID header with the last event id it received:
The server replays events that occurred after that id from its event log, then transitions to live streaming. This is gapless for events within the log’s retention window (default 7 days). The EventSource API in browsers handles Last-Event-ID automatically. Non-browser clients (SDKs, server-to-server consumers) should persist the most recent event id and re-send it on reconnect.

When the event log is stale

If Last-Event-ID is older than the event log retention window, the server replies with a terminal event indicating the gap:
Clients should recover by falling back to an incremental GET /items?updated_after=<timestamp>&limit=... sweep (paginated), then reconnect to live streaming. This path doesn’t surface deletes — items deleted during the gap aren’t returned. A subsequent SSE delete event will reconcile on receipt. Clients that need deterministic delete reconciliation after long outages should keep the SSE stream within retention or re-seed the local store.

Catch-up strategy

A typical sync engine follows this pattern:
1

First connection

Connect to GET /events without Last-Event-ID. Persist the most recent id received.
2

Live streaming

Apply events as they arrive. Persist Last-Event-ID on each event.
3

Reconnect on disconnect

Send persisted Last-Event-ID. Server replays from that point.
4

Handle `catchup_too_old`

Fall back to GET /items?updated_after=<last_sync_timestamp> with pagination. Reconnect live after catch-up completes.

Keepalive

The server sends :ping comment frames every ~30 seconds. Clients should close the connection and reconnect if no event (including pings) arrives for > 60 seconds — the connection is likely dead even if the TCP socket is open.

Scale

SSE is single-process on the emitting server. For deployments behind load balancers or multi-instance setups, configure sticky sessions or use the webhook delivery system instead — webhooks are durable and don’t require persistent connections.

SSE vs webhooks

Both fire on the same event surface — an item creation produces both an SSE event and a webhook delivery. Use whichever matches your consumer.