WebSocket sessions and recovery
Design authenticated subscriptions, sequence cursors, bounded buffers and reconnect behavior for live operational views.
On this page
Before you start
- A subscription authorization model
- A replay retention policy
- A bounded consumer buffer
Resume a subscription after a disconnect
Transport reconnection and application replay are separate steps. The cursor refers to the last committed application event.
Scroll across the diagram to see all four systems.
Establish secure session
Validate the origin and authenticated session under the selected deployment pattern.
Subscribe + cursor
Request the authorized stream with the last committed event cursor.
Check retained position
Verify cursor ownership and that its position is still available.
Replay retained events
Deliver the gap in order under the application sequence contract.
Publish next event
Continue from the replay boundary without silently skipping the gap.
Deliver live frame
The client applies the event and advances its committed cursor.
Separate WebSocket transport from the event contract
RFC 6455 defines an opening handshake, framing and connection control. It does not define subscriptions, event replay, delivery acknowledgement or business ordering. Those belong to the application protocol and must be documented as such.
Use wss for a secure browser connection. Validate browser Origin according to the application’s permitted origins, and authenticate the session under the chosen deployment pattern. A browser WebSocket constructor does not let application code set an arbitrary Authorization header; cookie sessions or short lived connection tickets need their own threat review.
Authorize every requested stream
A successfully opened socket is not permission to subscribe to every tenant or topic. Resolve subscription access from trusted identity, tenant membership and the requested resource. Reevaluate permission when membership changes or when a session is renewed.
Keep a compact application envelope with type, stream, event_id, sequence, occurred_at and payload. Define whether ordering applies to a stream, object or partition. A global order is expensive and often unnecessary when independent accounts can progress separately.
{
"subscribe": {
"type": "subscribe",
"stream": "tenant_a.contacts",
"after": "cursor_1042"
},
"event": {
"type": "event",
"stream": "tenant_a.contacts",
"event_id": "evt_1043",
"sequence": 1043,
"occurred_at": "2026-09-14T10:30:00Z",
"payload": {
"contact_id": "contact_240",
"stage": "qualified"
}
}
}Resume from a committed cursor
Advance the cursor after the client has applied an event to its intended state, not merely after bytes arrived. On reconnect, ask for events after that committed position. Apply the same event identity rules to replayed and live events so a repeated frame does not duplicate an action.
When the cursor falls outside retention, return an explicit resynchronization requirement. Fetch an authoritative snapshot with a defined stream position, then continue from that boundary. Quietly attaching to the live tail would leave an invisible data gap.
| State | Application action | Visible outcome |
|---|---|---|
| Connecting | Authenticate and request authorized subscriptions. | Show that the view is reconnecting. |
| Replaying | Apply retained events after the committed cursor. | Keep the last confirmed data timestamp visible. |
| Live | Process frames within the consumer budget. | Advance the cursor after application. |
| Cursor expired | Load a fresh snapshot and new boundary. | Explain that the view is resynchronizing. |
| Access removed | Close the subscription and stop protected updates. | Return a permission state rather than infinite reconnect. |
Bound the work a slow consumer can accumulate
MDN notes that the browser WebSocket API does not provide incoming backpressure. If messages arrive faster than the application processes them, memory and CPU use can grow. bufferedAmount describes outbound bytes queued by send; it is not an incoming backlog control.
Define application budgets: maximum queued events, maximum frame size and a policy for a slow consumer. For a replaceable dashboard value, coalescing updates may be appropriate. For an ordered ledger, disconnecting with an explicit resume or resync path is safer than silently dropping records.
Reconnect with a deadline and an observable state
Use bounded backoff with jitter after transient disconnections. Stop automatic reconnect for a confirmed permission denial until authority changes. Coordinate refresh so a page with several widgets does not create several competing sessions unnecessarily.
Protocol ping and pong frames are distinct from application heartbeat messages. Browser JavaScript does not directly control protocol ping frames. If the application defines a heartbeat, document what it measures and avoid treating it as proof that every downstream system is healthy.
Evaluate a subscription resume
Inspect application protocol decisions after a WebSocket session is established. Decision codes below describe this reference contract, not WebSocket close codes.
/api/v1/eventsExpected outcome
Resume accepted
Replay can start after the committed cursor before switching to live delivery.
{
"type": "subscribed",
"stream": "tenant_a.contacts",
"resume_after": "cursor_1042",
"mode": "replay_then_live"
}Application frames follow the established WebSocket session.
Decision codes are not protocol close codes.Decision path
- Authorize the subscription
- Locate the retained cursor
- Replay the gap
- Join live delivery at its boundary
Input schema 4 fields
| Field | Type | Meaning |
|---|---|---|
subscription_allowedRequired | boolean | Current permission for the requested stream. |
cursor_retainedRequired | boolean | Whether the replay position is still available. |
queued_eventsRequired | integer | Application events waiting to be applied.Minimum: 0 |
buffer_limitRequired | integer | Configured application queue budget.Minimum: 1 |
Standards and documentation
Explore the protocol specifications and platform guidance for this topic.
Bring the contract into your operating environment.
Discuss an implementation