Skip to content

Native lease context is part of the 2026-09-30 Statewire wire contract.

StatewireWebsocket carries frames and packets over WebSocket. StatewireHttp uses HTTP requests and an SSE stream.

Connections

An attach is one live subscription to an instance. It begins with a hello packet and ends when its socket or stream closes.

A client record holds durable-lane command history for a client id. It can serve several concurrent attaches. Ephemeral lanes, their documents, and every protocol's context belong to each attach.

  • Clients generate 32 lowercase hexadecimal characters. Servers accept [A-Za-z0-9._~-]{1,256}.
  • WebSocket carries the id in the client query parameter. HTTP stream requests carry it in Statewire-Client-Id.
  • Each attach receives its own snapshot. Attaches sharing a client id share durable-lane sequence numbers and command answers.

StatewireWebsocket

Open {url}/ws?client=<id> with subprotocol statewire.v1.

The first text message supplies transport headers:

{
  "headers": {
    "Statewire-Version": "\"2026-09-30\"",
    "Statewire-Protocol": "default; version=\"2026-09-30\""
  }
}
  • Additional offered subprotocols follow statewire.v1.
  • The initial message may also include ctx replacements and cmd statements (attach statements).
  • Send the initial message within 5 seconds, then wait for the hello before sending frames. The timeout is wsInitTimeoutMs / ws_init_timeout.
  • Send context replacements in socket order; hosts serialize their installation.
  • Send each later frame as one text message on the same socket. Each server text message contains one packet.
  • Command answers and admission refusals return over that socket; durable-lane answers also reach other attaches sharing the client id.

StatewireHttp

StatewireHttp receives packets through an SSE stream and submits later frames through separate HTTP requests.

A lease identifies one live attach. The stream response supplies it in Statewire-Lease; later frame requests present it in the same header.

GET /stream

Send GET /stream to receive state updates and command results:

GET /stream
Host: example.com
Statewire-Client-Id: 0123456789abcdef0123456789abcdef
Statewire-Version: "2026-09-30"
Statewire-Protocol: default; version="2026-09-30"

The response is an SSE stream (Content-Type: text/event-stream), starting with the hello packet.

200 OK
Content-Type: text/event-stream
Statewire-Lease: example-lease

data: {"syn":{"version":"2026-09-30","protocols":[{"name":"default","version":"2026-09-30","seq":0}]},"ops":[{"op":"mount","protocol":"default","main":true,"value":{"count":0}}]}

POST /stream

Opens the stream and immediately sends a frame in the request body:

POST /stream
Host: example.com
Statewire-Client-Id: 0123456789abcdef0123456789abcdef
Statewire-Version: "2026-09-30"
Statewire-Protocol: default; version="2026-09-30"
Content-Type: application/json

{"cmd": [{"seq": 1, "protocol": "default", "method": "increment", "params": [1]}]}
200 OK
Content-Type: text/event-stream
Statewire-Lease: example-lease

data: {"syn":{"version":"2026-09-30","protocols":[{"name":"default","version":"2026-09-30","seq":1}]},"ops":[{"op":"mount","protocol":"default","main":true,"value":{"count":0}}]}

data: {"ops":[{"op":"replace","path":["count"],"value":1}],"cmd":[{"protocol":"default","seq":1,"type":"applied"},{"protocol":"default","seq":1,"type":"result","payload":1}]}

Submit later frames through POST /frames.

Stream packets and leases

Each SSE data: event contains one packet, followed by a blank line:

data: {"idle":true}
  • Each concurrent attach has its own lease. Its authorization expires after the host's maximum lease age (default 24 hours); heartbeats and frame submissions preserve that deadline. Expiry sends fin: {reason: "reconnect", code: "lease-expired"}; a hibernating host (Cloudflare Durable Objects) sends it to a WebSocket attach on its next frame, its next packet, or when the host restores the socket. A frame using an expired lease receives 423 stale-lease; a new attach authorizes again. Hosts may disable expiry with maxLeaseLifetimeMs: null or max_lease_lifetime = None.
  • A response containing Statewire-Lease supports writes through POST /frames. If the header is omitted, submit statements in another POST /stream, opening another attach.
  • Cross-origin hosts expose the lease to browsers: Access-Control-Expose-Headers: Statewire-Lease. A browser treats an unexposed header as omitted.

POST /frames

Submits a frame for the attach identified by Statewire-Lease. The server resolves the lease to the client record, the attach's protocol contexts, ephemeral lanes and document view, and its authorization result.

POST /frames
Host: example.com
Statewire-Lease: example-lease
Content-Type: application/json

{"cmd": [{"seq": 1, "protocol": "default", "method": "increment", "params": [1]}]}
200 OK
Content-Type: application/json

{}

Use the lease returned by the stream in place of example-lease. Command answers arrive on the stream.

StatusBodyMeaning and next action
200{}Context installed and commands admitted. Mark statements posted and await their stream answers. Context-only frames require no command answer.
409{"syn": {"protocols": [{"name": ..., "seq": ...}]}}Context installed; new commands refused for a sequence gap. Reconcile with the per-lane watermarks and resend commands.
429{"syn": {"protocols": [{"name": ..., "seq": ...}], "retry": ...}}Host admission pushback leaves context installed and new commands unadmitted. Wait, then resend commands. A lower-layer 429 does not confirm context installation.
423{"error": "stale-lease"}Lease is stale. Reattach with the latest retained context, then resend pending durable-lane commands under their original sequence numbers, omitting the stale frame's ctx. Unanswered ephemeral-lane commands are lost; newly queued ephemeral commands start at 1.
400{"error": "<reason>"}Protocol violation. The stream also ends with fin: protocol-error.
413{"error": "frame body too large"}Body exceeds 8 MiB of UTF-8. Split the unadmitted frame.
  • A valid lease supplies the attach's captured authorization result for the submitted commands.
  • On 429, syn.retry is the delay in milliseconds. Retry-After rounds it up to whole seconds. Clients use a parseable delta-seconds Retry-After when a lower layer returns 429 without a usable refusal body.
  • Allow one POST in flight per lane. A frame spanning several lanes occupies each of them until its POST completes.
  • Context-bearing POSTs occupy the attach's context write channel until they complete. Serialize context writes with command frames whose admission reads that context.
  • After an uncertain outcome of a context-bearing POST, retire its lease, attach with the latest retained context, and resend pending durable commands under their original sequence numbers. Hosts fence late requests and context-change work to their originating attach.
  • After a timeout of a command-only POST, resend the original statements under their sequence numbers. The retry may append more consecutive statements. Admission and recovery handle repeated submissions.

Authorization

Authorization runs once per attach and binds its result to that connection. Credential changes require a new attach.

  • StatewireHttp authorizes /stream using HTTP request headers. /frames uses the authorization result associated with its lease. A headers field in any HTTP frame is a protocol violation.
  • StatewireWebsocket carries the required negotiation headers in its initial message. These headers fill names absent from the WebSocket handshake headers. A handshake header wins a collision.
  • Header names and values in the initial message must be valid. Duplicate names are rejected before authorization.
  • Later StatewireWebsocket frames containing headers are a protocol violation.
Attach problemHTTPWebSocket
Missing initial-message headers—fin: unauthorized, status 401, then close 1008
Malformed initial-message headers—fin: protocol-error, message malformed headers, then 1008
First-frame timeout—fin: unauthorized, then 1008
Credentials missing, invalid, or expired401fin: unauthorized, then 1008
Authorization denies access403fin: forbidden, then 1008
Different authorization result for an existing client id403fin: forbidden, then 1008

Concurrent attaches and record lifetime

The first attach binds the client id to its authorization result. Later attaches must match it until the client record is evicted.

  • Ephemeral lanes and their documents end with their attach; each attach restates them from seq 1.
  • Idle records are evicted least-recently-used. Live records and records holding commands are exempt.
  • Protocol context ends with its attach; clients restate the latest values on each new attach, including preserved-socket wake. Already admitted commands retain their captured context.
  • Durable-lane admission survives host restarts. Commands replayed after a restart run without an attach.
LimitSettingDefault
Concurrent subscribersmaxSubscribers / max_subscribers1024
Client recordsclientRecordLimit / client_record_limit1024

An attach exceeding the subscriber cap receives HTTP 503, or WebSocket fin: reconnect with code: overloaded, followed by 1013.

Version negotiation

  • Dated attaches require Statewire-Version and Statewire-Protocol. They use inclusive date ranges.
  • WebSocket requires the fixed bootstrap subprotocol statewire.v1.
  • HTTP and WebSocket return selected versions in syn.version and syn.protocols before application state.
  • Invalid negotiation returns HTTP 400; unsupported ranges or required protocols return 406. The JSON body carries fin: protocol-error with code: invalid-negotiation, unsupported-version, or unsupported-protocol.
  • WebSocket sends the same fin and closes with 1008.
  • Each lease retains its attach's selection. Negotiation headers on /frames leave that selection unchanged.
  • Initial commands are accepted with version ranges. Clients tighten their minimums when initial statements require newer features.

Heartbeats

A heartbeat keeps an otherwise quiet connection alive.

TransportHeartbeat
WebSocketEmpty object {}
SSEComment : ping
  • Hosts send a heartbeat after 15 seconds without outgoing bytes.
  • WebSocket heartbeats flow both ways. Clients send {} every 10 seconds once the attach frame is sent.
  • Hosts answer a client {} with {} once the attach frame is received. A heartbeat is never a frame or a packet.
  • After 30 seconds of receive silence, clients reconnect.
  • The same 30-second timeout applies while waiting for the initial hello and to each POST.

Violations

A violation is invalid client input, such as a malformed frame or a broken sequence within a lane. A gap relative to the server watermark is an admission refusal.

InputWebSocketHTTP
Malformed frame or another frame-rule violationfin: protocol-error, then 1008400; a violating POST /frames also ends the stream
Text frame or body over 8 MiB of UTF-8fin: protocol-error, then 1009413 before parsing
Invalid client id1008400
  • A missing client id on /stream returns 400 {"detail":"missing Statewire-Client-Id header"}. An invalid id returns 400 {"detail":"invalid Statewire-Client-Id: <reason>"}.
  • Protocol violations stop automatic reconnection.
  • Client actions for fin reasons follow Connection ending.

Host handling

  • Initial ctx is validated and installed after protocol selection and authorization, before the hello and initial commands. Later WebSocket frames received during authorization are processed after the hello.
  • Validate WebSocket client ids before accepting the socket where the adapter supports it.
  • Binary WebSocket frames receive fin: protocol-error, message malformed frame, then close 1008.
  • A pending WebSocket attach occupies a subscriber slot after its initial message.
  • An outgoing queue exceeding streamQueueSize / stream_queue_size (default 256) ends with fin: reconnect, code: client-fell-behind.
  • Unexpected authorization or activation failures end with fin: error and WebSocket close 1011.