HomeJournalThis post

Build a resumable WebSocket client

A resumable client combines explicit states, ordered cursors, replay, snapshot fallback, idempotent outgoing effects, backpressure, reconnect jitter, and convergence tests.

JP
JP Casabianca
Designer/Engineer · Bogotá

Reconnect is transport recovery. Resume is product recovery.

A WebSocket can close after the server committed an action but before the client received its event. It can reopen on another node after missing fifty updates. A sleeping tab can wake with a valid object and stale product state. Simply constructing a new socket gives none of those gaps a meaning.

The living WHATWG WebSockets specification defines connection states, events, close behavior, and bufferedAmount; it does not define application replay, acknowledgement, or convergence. Those are protocol decisions the product has to add above the transport.

I would give every ordered stream a durable cursor, let the server accept a bounded resume request, fall back to a snapshot when history is unavailable, and keep outgoing effects idempotent across ambiguous disconnects.

The success state is not connected. It is caught up.

01 · OpenEstablish and authenticate

Negotiate protocol, bind user and stream scope, advertise last durable cursor, and reject stale credentials without loops.

02 · ResumeClose the event gap

Server replays after cursor in order or declares history unavailable; client deduplicates and applies through one reducer.

03 · ConvergeBecome live deliberately

Snapshot fallback, replay watermark, outgoing acknowledgements, bufferedAmount, and UI state agree before live status appears.

Figure 1: Connection recovery ends only after state converges.

Model client states explicitly

Idle, connecting, authenticating, recovering, live, backing off, offline, paused, and terminal failure should drive UI and side effects through one reducer.

I would pressure-test that decision with four questions:

  • Is the socket open but stale?
  • Can this state accept sends?
  • Which timer owns transition?
  • What should the user see?

The failure mode here is representing the entire protocol with one connected boolean. In real-time web clients for collaboration, operations, messaging, dashboards, presence, and long-running tasks where networks change, tabs sleep, servers deploy, credentials expire, messages burst, and disconnects hide what the client missed, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a WebSocket client state chart. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is legal transitions and product copy verified under every close path. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a WebSocket client state chart beside the question “Is the socket open but stale?” before the first implementation review. The next pass would use “Can this state accept sends?” to test the boundary, then “Which timer owns transition?” to expose the state most likely to be missed. I would keep “What should the user see?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports legal transitions and product copy verified under every close path.

Define ordered stream identity

Cursor meaning depends on stream scope, monotonic sequence, schema version, retention, partition behavior, and whether order is global or aggregate-local.

The practical review starts here:

  • What does sequence order?
  • Can streams move partitions?
  • How long is replay retained?
  • Does schema change invalidate cursor?

Those questions keep storing the last event ID without documenting its scope or stability from becoming the default. I would capture the decision in a stream cursor contract, then use it while the work is still cheap to change. For real-time interfaces that recover state rather than merely reopen sockets, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like resume requests selecting one unambiguous contiguous history. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a stream cursor contract part of the working surface. I would use it to answer “What does sequence order?” while scope is still flexible, and “Can streams move partitions?” before code or content becomes expensive to unwind. During QA, “How long is replay retained?” and “Does schema change invalidate cursor?” become concrete checks rather than discussion prompts. That sequence turns real-time interfaces that recover state rather than merely reopen sockets into something the team can operate and gives me a specific outcome to report: resume requests selecting one unambiguous contiguous history.

  1. DisconnectedOffline, idle, or backing off

    Keep safe local intent, expose last update, cancel obsolete timers, and choose whether visibility permits reconnect.

  2. RecoveringOpen but not caught up

    Authenticate, negotiate resume, apply replay or snapshot, withhold stale actions, and surface delayed state.

  3. LiveCursor and server watermark agree

    Process ordered events, ack policy, heartbeat, bounded sends, credential changes, and controlled close.

Figure 2: The client state machine distinguishes transport from product readiness.

Persist only applied progress

Advance the durable cursor after the event validates and the reducer commits, not when bytes arrive, so crashes and render failures cannot skip state.

Before implementation, I would answer:

  • When is an event applied?
  • Is cursor write atomic enough?
  • Can duplicate application hurt?
  • What survives reload?

The artifact is an event-apply and cursor checkpoint. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is checkpointing on message receipt before application completes; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is reload replaying at worst a harmless duplicate instead of losing an event. That connects an application-level stream protocol with connection states, session authentication, monotonic cursors, replay windows, snapshot fallback, acknowledgement, bounded buffering, jittered reconnect, visibility policy, and observable convergence to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “When is an event applied?” easy to answer. The boundary should force a decision about “Is cursor write atomic enough?” and “Can duplicate application hurt?.” I would record both in an event-apply and cursor checkpoint, including the part that stayed unresolved after the first pass. The final check, “What survives reload?,” is where the artifact earns its place: it either supports reload replaying at worst a harmless duplicate instead of losing an event, or it shows exactly why another iteration is needed.

Negotiate resume on every session

The handshake should bind auth, user, tenant, stream, client protocol, last cursor, and desired capabilities before live events begin.

I would use these prompts during the working review:

  • Who owns this cursor?
  • Can auth change during sleep?
  • Which protocol version is selected?
  • When may live delivery start?

If the team slips into sending a cursor before the server knows the authorized stream scope, the product can still look complete while its operating rule stays ambiguous. I would make a typed hello and resume envelope the shared reference and keep it small enough to update as evidence changes.

The standard is cross-account and stale-session resumes rejected without leaking stream existence. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft a typed hello and resume envelope, review it against “Who owns this cursor?,” implement the narrowest useful path, and then return with evidence for “Can auth change during sleep?.” I would use “Which protocol version is selected?” to inspect product consequence and “When may live delivery start?” to decide whether the result is stable enough to ship. This keeps sending a cursor before the server knows the authorized stream scope visible as a known risk and makes cross-account and stale-session resumes rejected without leaking stream existence the release receipt rather than a hopeful conclusion.

SignalDecisionWorking note
ReplayCursor remains in retentionDeliver ordered events after the last applied sequence, include watermark, deduplicate, and advance only after reducer commit.
SnapshotHistory expired or schema changedFetch a consistent current state and cursor, replace owned view atomically, then subscribe after that boundary.
RestartSession or authority is invalidClear unsafe queues, refresh credentials or require user action, and never spin on a permanent close reason.
Figure 3: Recovery choices depend on the gap.

Fall back to a consistent snapshot

When replay history is missing, snapshot data and its cursor need one consistency boundary so events do not slip between fetch and subscription.

I would pressure-test that decision with four questions:

  • Which cursor belongs to snapshot?
  • Can live events race it?
  • Is replacement atomic?
  • How is large state paged?

The failure mode here is fetching current state and then subscribing without a boundary. In real-time web clients for collaboration, operations, messaging, dashboards, presence, and long-running tasks where networks change, tabs sleep, servers deploy, credentials expire, messages burst, and disconnects hide what the client missed, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a snapshot-to-live transition protocol. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is history expiry still converging to the same state as uninterrupted delivery. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a snapshot-to-live transition protocol beside the question “Which cursor belongs to snapshot?” before the first implementation review. The next pass would use “Can live events race it?” to test the boundary, then “Is replacement atomic?” to expose the state most likely to be missed. I would keep “How is large state paged?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports history expiry still converging to the same state as uninterrupted delivery.

Make outgoing effects replay-safe

Client operation IDs, optimistic state, server deduplication, acknowledgement, retry policy, and reconciliation should handle disconnect after commit but before response.

The practical review starts here:

  • What identifies the intent?
  • Can it be sent twice?
  • How is optimistic state settled?
  • When must the user decide?

Those questions keep emptying a memory queue on reopen and duplicating ambiguous actions from becoming the default. I would capture the decision in an outgoing operation ledger, then use it while the work is still cheap to change. For real-time interfaces that recover state rather than merely reopen sockets, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like each logical user action reaching one durable terminal outcome. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make an outgoing operation ledger part of the working surface. I would use it to answer “What identifies the intent?” while scope is still flexible, and “Can it be sent twice?” before code or content becomes expensive to unwind. During QA, “How is optimistic state settled?” and “When must the user decide?” become concrete checks rather than discussion prompts. That sequence turns real-time interfaces that recover state rather than merely reopen sockets into something the team can operate and gives me a specific outcome to report: each logical user action reaching one durable terminal outcome.

Respect backpressure

bufferedAmount, message size, incoming apply cost, outgoing queue units, high-water marks, batching, and overload policy need budgets because WebSocket itself does not pace application production.

Before implementation, I would answer:

  • Which queue is growing?
  • What unit measures pressure?
  • When are sends paused?
  • Which data may be coalesced?

The artifact is a socket pressure budget. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is checking readyState while an unbounded send buffer grows; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is memory, input latency, and catch-up time staying bounded under burst fixtures. That connects an application-level stream protocol with connection states, session authentication, monotonic cursors, replay windows, snapshot fallback, acknowledgement, bounded buffering, jittered reconnect, visibility policy, and observable convergence to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “Which queue is growing?” easy to answer. The boundary should force a decision about “What unit measures pressure?” and “When are sends paused?.” I would record both in a socket pressure budget, including the part that stayed unresolved after the first pass. The final check, “Which data may be coalesced?,” is where the artifact earns its place: it either supports memory, input latency, and catch-up time staying bounded under burst fixtures, or it shows exactly why another iteration is needed.

Reconnect with bounded jitter

Exponential delay, randomized jitter, online and visibility signals, Retry-After-like server hints, maximum attempts, and permanent close codes should prevent synchronized reconnect storms.

I would use these prompts during the working review:

  • Which failures are retryable?
  • How is delay randomized?
  • Does a hidden tab reconnect?
  • When does automation stop?

If the team slips into reopening immediately from every close handler, the product can still look complete while its operating rule stays ambiguous. I would make a reconnect decision table the shared reference and keep it small enough to update as evidence changes.

The standard is thousands of clients recovering without a second outage or endless loop. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft a reconnect decision table, review it against “Which failures are retryable?,” implement the narrowest useful path, and then return with evidence for “How is delay randomized?.” I would use “Does a hidden tab reconnect?” to inspect product consequence and “When does automation stop?” to decide whether the result is stable enough to ship. This keeps reopening immediately from every close handler visible as a known risk and makes thousands of clients recovering without a second outage or endless loop the release receipt rather than a hopeful conclusion.

Coordinate auth and tab lifecycle

Token refresh, logout, account switch, page freeze, network change, and multiple tabs should close or transfer authority without using stale credentials or multiplying connections unnecessarily.

I would pressure-test that decision with four questions:

  • Who refreshes credentials?
  • What does logout invalidate?
  • Can a frozen tab remain leader?
  • Does each tab need a socket?

The failure mode here is treating sockets as independent of session and browser lifecycle. In real-time web clients for collaboration, operations, messaging, dashboards, presence, and long-running tasks where networks change, tabs sleep, servers deploy, credentials expire, messages burst, and disconnects hide what the client missed, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a lifecycle and connection-ownership plan. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is account changes and tab suspension producing no cross-user events or ghost connections. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a lifecycle and connection-ownership plan beside the question “Who refreshes credentials?” before the first implementation review. The next pass would use “What does logout invalidate?” to test the boundary, then “Can a frozen tab remain leader?” to expose the state most likely to be missed. I would keep “Does each tab need a socket?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports account changes and tab suspension producing no cross-user events or ghost connections.

Test convergence, not open events

A harness should drop frames, reorder where the protocol allows, expire history, duplicate messages, split acknowledgements, restart servers, rotate auth, overflow queues, and freeze tabs.

The practical review starts here:

  • Does final state converge?
  • Can any effect duplicate?
  • Is cursor monotonic?
  • Does UI reveal recovery accurately?

Those questions keep asserting that onopen fires after a development reconnect from becoming the default. I would capture the decision in a deterministic fault-injection socket server, then use it while the work is still cheap to change. For real-time interfaces that recover state rather than merely reopen sockets, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like the recovered client matching an uninterrupted reference state across faults. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a deterministic fault-injection socket server part of the working surface. I would use it to answer “Does final state converge?” while scope is still flexible, and “Can any effect duplicate?” before code or content becomes expensive to unwind. During QA, “Is cursor monotonic?” and “Does UI reveal recovery accurately?” become concrete checks rather than discussion prompts. That sequence turns real-time interfaces that recover state rather than merely reopen sockets into something the team can operate and gives me a specific outcome to report: the recovered client matching an uninterrupted reference state across faults.

What I would show in the work

The public version needs evidence from the work itself. For this topic, the first five artifacts I would reach for are:

  • a WebSocket client state chart
  • a stream cursor contract
  • an event-apply and cursor checkpoint
  • a typed hello and resume envelope
  • a snapshot-to-live transition protocol

I would not publish all five at equal weight. One should orient the reader, one should reveal the hardest tradeoff, and one should prove the result. The others can live in a downloadable note or appear as supporting frames. That edit matters because an application-level stream protocol with connection states, session authentication, monotonic cursors, replay windows, snapshot fallback, acknowledgement, bounded buffering, jittered reconnect, visibility policy, and observable convergence becomes harder to understand when every process detail is treated as equally important.

I would also show one rejected direction. The useful version is specific: which option looked attractive, which constraint made it wrong, and what evidence supported the narrower choice. That gives an engineering manager something real to question and keeps the case study from reading like the final answer was obvious from the beginning.

socket-session-trace.json
# hello
stream project:42 / cursor 781 / schema 3
Client instance c_9, auth binding a_6, supported compression none, outgoing queue 2, last server time 20:02Z.

# resume accepted / replay 782..816 / liveAt 816 Events carry stream, sequence, event ID, causation ID, schema, payload; client applies through one ordered reducer.

# receipt cursor 816 persisted / queue 0 / state live Duplicate 793 ignored, outgoing op_12 acknowledged once, reconnect attempt 3 after 1.8s jitter, catch-up 214ms.

Figure 4: The envelope makes resumption testable.

Resource path

The practical follow-up I would build is a resumable WebSocket client kit with state reducer, protocol envelope, cursor persistence, handshake, replay and snapshot paths, acknowledgement policy, outgoing queue, backpressure thresholds, heartbeat, reconnect jitter, auth refresh, close-code map, multi-tab ownership, telemetry, test server, and failure fixtures. I am treating that as a resource backlog item, not pretending the adjacent downloads below are the same artifact. The related cards cover useful pieces of the workflow today; this specific file should only be published when its examples, fields, and instructions are complete.

The first version should stay concise: context, constraint, decision, evidence, owner, and follow-up. Its value would come from helping someone repeat this exact review, not from adding another generic PDF to the site.

Review checklist

The article-specific review questions are:

  • Is the socket open but stale?
  • What does sequence order?
  • When is an event applied?
  • Who owns this cursor?
  • Which cursor belongs to snapshot?
  • What identifies the intent?
  • Which queue is growing?
  • Which failures are retryable?
  • Who refreshes credentials?
  • Does final state converge?

I would add two editorial checks before publishing: can a recruiter find the point in the first minute, and can an engineer trace at least one claim to an implementation or production receipt? If either answer is no, the article needs another edit.

Implementation notes

For real-time interfaces that recover state rather than merely reopen sockets, I would write the implementation note before polish. It would name the changed surface, source of truth, owner, failure boundary, and verification path. Those details prevent the principle from floating above the actual code or operational workflow.

The proof signals I care about are specific to this article:

  • each logical user action reaching one durable terminal outcome
  • memory, input latency, and catch-up time staying bounded under burst fixtures
  • thousands of clients recovering without a second outage or endless loop
  • account changes and tab suspension producing no cross-user events or ghost connections
  • the recovered client matching an uninterrupted reference state across faults

I would choose two or three of those signals for the first release rather than instrumenting everything. The strongest pair usually combines one direct behavior check with one operating check: a route and a data query, a keyboard path and a support state, a handler replay and a reconciliation result, or a migration count and a rendered screen.

The follow-up belongs in the note before shipping. It should say what remains temporary, what evidence would trigger another pass, and who owns that decision. That is how the first version stays intentionally narrow without making the boundary invisible.

Case-study packaging

I would structure the case-study version around the four visual lessons already established:

  • Connection recovery ends only after state converges.
  • The client state machine distinguishes transport from product readiness.
  • Recovery choices depend on the gap.
  • The envelope makes resumption testable.

The opening frame explains the product pressure. The middle two show the decision moving through the system. The last frame is the receipt: what was checked, what held, and what remained unresolved. That order lets the reader move from product judgment into implementation detail without reconstructing the whole project first.

I would include one caveat tied to real-time web clients for collaboration, operations, messaging, dashboards, presence, and long-running tasks where networks change, tabs sleep, servers deploy, credentials expire, messages burst, and disconnects hide what the client missed: a data limit, rollout boundary, unsupported state, external dependency, or result that is still directional. A precise caveat makes the evidence easier to trust because it shows where the claim stops.

The final test is whether the page creates a better conversation. If the artifact helps someone ask a sharper question about product judgment, implementation detail, or release proof in a live interview, it belongs in the story.

Interview angle

In an interview, I would explain this through an application-level stream protocol with connection states, session authentication, monotonic cursors, replay windows, snapshot fallback, acknowledgement, bounded buffering, jittered reconnect, visibility policy, and observable convergence. The story should start with the product pressure, then move into the system constraint, the artifact, and the proof. That order keeps the answer grounded. It also gives the interviewer several places to go deeper: data, frontend architecture, design systems, support, migration, accessibility, or release process.

The strongest version of the answer includes a tradeoff. I want to be able to say what I chose, what I left alone, and how I knew the work helped. That is more credible than presenting every project as a clean win.

The hiring signal

A resumable WebSocket client is a hiring signal because it shows I can connect browser lifecycle, application protocols, ordering, authentication, backpressure, state convergence, and user-visible recovery.

That is the level I want this site to communicate. The work should show taste, but it should also show operating judgment. It should make me look like someone who can enter a real product system, understand the messy middle, ship the useful version, and leave enough proof for the next person to trust it.

Companion artifacts

Use this after reading.

Practical downloads and templates that turn the article into something you can bring into a product review, implementation pass, or agent workflow.

DownloadJun 2026

Front-End State Recipes

Reusable recipes for optimistic actions, loading, empty, error, data-transition, and disabled-control states.

FrontendStatesUX
View details
RepoJun 2026

Agent-Ready API Spec Template

An OpenAPI and Postman starter template for APIs that AI agents can discover, call, and recover from safely.

OpenAPIPostmanAI agents
View details
DownloadJun 2026

UI PR Risk Review Checklist

A merge-readiness checklist for product intent, states, accessibility, visual durability, and UI implementation risk.

UI reviewQAFrontend
View details