flashman
← All guides

Resumable Server-Sent Events design

Design resumable Server-Sent Events with durable event IDs, Last-Event-ID replay, bounded retention, proxy streaming, and duplicate-safe client handling.

2026-09-29 · 8 min read

  • http
  • sse
  • reliability

Server-Sent Events are a one-way HTTP stream. The client reconnects with the Last-Event-ID header when the connection drops, but only if the server put an id on the events and kept enough history to replay after that id.

A live demo that works on localhost can still lose messages behind a buffering proxy or duplicate them when the browser retries an event it already handled.

Give every resumable event a position

Choose an identifier the server can order for that stream: a per-subscriber sequence, a log offset, or another monotonic position. A random id proves uniqueness and does not define what to replay.

  • Set the id field on every event that must survive reconnect.
  • End each event with a blank line.
  • Keep data as UTF-8 text, often one JSON object.
  • Use the retry field only with a deliberate backoff policy.

Store a bounded replay window

Persist events, or the ability to recompute them, for longer than a realistic disconnect. Define the response when Last-Event-ID is unknown, expired, or ahead of the log. Catch-up from empty and a full snapshot are different products; pick one and document it.

Authorize the stream and the replay on every request. An unguessable URL is not a substitute for authentication, because reconnects and logs copy that URL.

Make clients idempotent

The client can apply an event and then disconnect before it has a newer id to resume from. The server will send that event again. Handlers should treat the id as an idempotency key.

Ignore comments and unknown event names without closing the stream. Surface application error events differently from an HTTP failure so reconnect logic stays intact.

Disable buffering on the path

Send text/event-stream and headers that tell proxies not to buffer. Confirm idle timeouts are longer than your heartbeat, and send a comment heartbeat if the platform closes quiet connections.

Compression and intermediate caches can hold bytes until a buffer fills. Test the deployed path, not only a direct socket to the origin.

Rehearse disconnects

Use Flashman's JSON formatter for event data, the timestamp converter for heartbeat intervals, the UUID generator when fixtures need synthetic ids, the URL parser for the stream request, and the Markdown editor for the replay contract.

Test a drop after a stored id, an event with no id, an expired window, a duplicate delivery, proxy buffering, an idle timeout, an unauthorized reconnect, and a client that retries faster than the server allows.

Try these tools