Skip to navigation

WebSocket frames v1

Exact application-frame names, types, and parser bounds.

View as Markdown

This page describes the v1 application wire format. Values such as REPLACE_WITH_... are placeholders, not usable credentials or proof that a server accepted a session. Event payloads carried inside batch.events are defined in the event reference.

Common encoding rules

ValueWire representation and bound
versionJSON integer. The current version is 1.
frameSequenceCanonical decimal string from 1 through u64::MAX, allocated per connection.
PositionCanonical decimal string from 0 through i64::MAX. No sign or leading zeroes.
GenerationCanonical decimal string from 1 through u64::MAX. No sign or leading zeroes.
UUIDA UUID value is serialized as a lowercase hyphenated string (36 characters). The ACK parser rejects a nil connectionId; the server serializer rejects nil hello connection/stream/history IDs and nil batchId.
Hash32Serializer output is canonical lowercase 0x followed by 64 lowercase hexadecimal characters.
Client ID1–128 ASCII characters: letters, digits, ., :, _, or -.
Ticket or opaque resume token1–2048 ASCII characters: letters, digits, ., _, or -. A resume token is not an authentication credential.

Positions and generations are different types: positions are bounded by signed 64-bit storage, while generations are unsigned 64-bit values. Frame sequences and generations are serialized from positive u64 counters. Counts embedded in a batch have their own bounds below.

All client frames require version and kind; unknown fields are rejected. Client frames are limited to 256 KiB. The server serializer limits application frames to 20 MiB.

Client frames

Every subscribe and rebind requires a filters object. Its selector fields are owners, tokenIds, conditionIds, marketSlugs, and stages. Each field may be omitted or set to null, which both mean “not supplied.” A supplied array must contain 1–256 entries; an empty array is invalid.

kindRequired fieldsPurpose
authenticateticketPresent the operator-issued API key for the approved account.
subscriberequestId, consumerId, subscriptionId, filters, startCreate a subscription or resume one using its saved token.
rebindrequestId, subscriptionId, expectedGeneration, filters, startExplicitly change the filter binding for an existing subscription.
acktoken, connectionId, generationAcknowledge a checkpoint after the corresponding client state is durable.

The three client-ID fields use the client-ID grammar above. ticket, resume token, and ACK token use the bounded token grammar. expectedGeneration and ACK generation are positive canonical decimal strings within u64.

The parser applies a separate market-slug rule: each marketSlugs value is 1–256 lowercase ASCII letters, digits, or hyphens, with no leading or trailing hyphen. Other selector values first pass the client-ID character and length bound; selector meaning and account authorization are checked separately.

start has exactly one of these shapes:

{"mode":"live_cut"}
{"mode":"resume","token":"REPLACE_WITH_SAVED_CHECKPOINT"}

The resume token is opaque and bound to its history, subscription, and filter. Do not edit its position or treat it as authentication.

Server envelope

Every application frame has the common version (JSON integer 1), positive decimal-string frameSequence, and one of seven kind values: hello, subscribed, batch, checkpoint, caught_up, reset_required, or error. Native WebSocket Ping/Pong control frames are not application frames. The following complete synthetic hello and subscribed lines are from the serializer-backed control sequence fixture; identifiers and state are examples, not evidence of a live session:

{"version":1,"frameSequence":"1","kind":"hello","versions":[1],"connectionId":"11111111-1111-4111-8111-111111111111","streamId":"22222222-2222-4222-8222-222222222222","historyGeneration":"33333333-3333-4333-8333-333333333333","committedFrontier":"42","scope":{"namespace":"onchain.lifecycle.v1","network":"polygon:137"},"capabilities":{"replay":true,"clientAck":true,"sourceQuality":true},"retention":{"resumeFloor":"0","policyVersion":"1"},"limits":{"authTimeoutMs":"10000","subscribeTimeoutMs":"10000","sendTimeoutMs":"1000","maxPageBatches":"10","maxPageBytes":"65536","replayLeaseTtlMs":"30000","replayLeaseByteBudget":"1048576","maxUnackedCount":"10","maxUnackedBytes":"1048576","maxUnackedAgeMs":"30000","maxClientFrameBytes":"262144","maxServerFrameBytes":"20971520"},"heartbeat":{"mode":"client_ping_pong","serverPingIntervalMs":null},"health":{"delivery":"running"}}
{"version":1,"frameSequence":"2","kind":"subscribed","subscriptionId":"wallet-1","generation":"1","filterHash":"0x0101010101010101010101010101010101010101010101010101010101010101","filterPolicyVersion":"1","startPosition":"42","cutPosition":"42","sourceQuality":[{"sourceScope":"polygon.pending","network":"polygon:137","contractScope":"ctf-exchange-v2","capability":"unknown","lastPosition":null}]}

hello

versions is an array of JSON integers; the current supported value is 1. connectionId, streamId, and historyGeneration are non-nil UUIDs. committedFrontier and retention.resumeFloor are positions. The scope fields are exactly namespace: "onchain.lifecycle.v1" and network: "polygon:137"; capabilities contains boolean replay, clientAck, and sourceQuality flags. These flags do not grant account scope.

retention.policyVersion is a positive u64 decimal string. Every limits field is a decimal string:

FieldMeaning
authTimeoutMsAuthentication deadline in milliseconds.
subscribeTimeoutMsSubscribe deadline in milliseconds.
sendTimeoutMsSend deadline in milliseconds.
maxPageBatchesMaximum batches in a replay page.
maxPageBytesMaximum replay-page bytes.
replayLeaseTtlMsReplay lease time-to-live in milliseconds.
replayLeaseByteBudgetReplay lease byte budget.
maxUnackedCountMaximum unacknowledged checkpoints.
maxUnackedBytesMaximum unacknowledged bytes.
maxUnackedAgeMsMaximum unacknowledged age in milliseconds.
maxClientFrameBytesMaximum client application frame: 262144 bytes.
maxServerFrameBytesMaximum server application frame: 20971520 bytes.

heartbeat.mode is client_ping_pong; serverPingIntervalMs is JSON null for this mode (native WebSocket control frames are separate). health.delivery is one of created, running, stopped, history_unavailable, or failed.

subscribed

This frame reports the accepted subscriptionId, positive generation, filterHash, filterPolicyVersion, startPosition, cutPosition, and sourceQuality. The start position must not exceed the cut. filterHash is canonical Hash32; filterPolicyVersion is a positive u32 serialized as a decimal string.

sourceQuality contains 1–16 unique source/network/contract scopes:

FieldWire type and rule
sourceScope1–128 ASCII characters: lowercase letters, digits, ., :, _, or -.
networkThe string polygon:137.
contractScope1–128 ASCII characters using the same grammar as sourceScope.
capabilityunknown, ready, or gap.
lastPositionnull for unknown; for ready or gap, a positive position no greater than cutPosition.

Uniqueness is by the (sourceScope, network, contractScope) tuple. This is a minimal quality snapshot; it is not a private source recovery record.

frozenMarkets and frozenConditions are independently optional: when absent from the server state the serializer omits the corresponding JSON key (it does not emit null). When present, each is the same frozen catalog-snapshot shape:

FieldWire type and rule
versionJSON integer 1.
networkNetworkRef: namespace is eip155, chain_id is JSON integer 137, and genesis_hash is canonical Hash32. This differs from the scope.network string.
publication.generationNon-nil UUID.
publication.revisionPositive u64 decimal string.
publication.manifestHashCanonical Hash32.
publication.manifestAlgorithm1–64 ASCII alphanumeric, ., -, or _ characters.
publication.publishedAtRFC 3339 timestamp, at most 64 characters.
markets1–256 selected catalog rows, sorted by slug in the canonical snapshot.
markets[].marketId1–64 ASCII digits, no leading zero.
markets[].slug1–256 lowercase ASCII letters, digits, or hyphens; no leading or trailing hyphen.
markets[].mappingRevisionPositive u64 decimal string.
markets[].conditionIdNonzero canonical Hash32.
markets[].tokenIds1–256 unique, nonzero uint256 canonical decimal strings per row.

frozenMarkets resolves the marketSlugs selector; frozenConditions resolves conditionIds. Each selected snapshot is bounded to 64 KiB. These are immutable selection snapshots, not a claim of current catalog freshness or coverage.

This complete synthetic subscribed line shows both optional catalog fields populated. It is an independent serializer fixture, not a continuation of the wallet quickstart, a live capture, or proof of authentication or admission:

{
"cutPosition": "2",
"filterHash": "0x0202020202020202020202020202020202020202020202020202020202020202",
"filterPolicyVersion": "1",
"frameSequence": "2",
"frozenConditions": {
"markets": [
{
"conditionId": "0x0101010101010101010101010101010101010101010101010101010101010101",
"mappingRevision": "1",
"marketId": "10",
"slug": "market-one",
"tokenIds": [
"7",
"8"
]
}
],
"network": {
"chain_id": 137,
"genesis_hash": "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b",
"namespace": "eip155"
},
"publication": {
"generation": "11111111-1111-4111-8111-111111111111",
"manifestAlgorithm": "sha256-v1",
"manifestHash": "0x0707070707070707070707070707070707070707070707070707070707070707",
"publishedAt": "2026-09-27T00:00:00Z",
"revision": "1"
},
"version": 1
},
"frozenMarkets": {
"markets": [
{
"conditionId": "0x0101010101010101010101010101010101010101010101010101010101010101",
"mappingRevision": "1",
"marketId": "10",
"slug": "market-one",
"tokenIds": [
"7",
"8"
]
}
],
"network": {
"chain_id": 137,
"genesis_hash": "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b",
"namespace": "eip155"
},
"publication": {
"generation": "11111111-1111-4111-8111-111111111111",
"manifestAlgorithm": "sha256-v1",
"manifestHash": "0x0707070707070707070707070707070707070707070707070707070707070707",
"publishedAt": "2026-09-27T00:00:00Z",
"revision": "1"
},
"version": 1
},
"generation": "2",
"kind": "subscribed",
"sourceQuality": [
{
"capability": "unknown",
"contractScope": "ctf-exchange-v2",
"lastPosition": null,
"network": "polygon:137",
"sourceScope": "polygon.pending"
}
],
"startPosition": "1",
"subscriptionId": "market-1",
"version": 1
}

batch

FieldWire type and rule
positionPosition string for the atomic journal batch.
batchIdNon-nil UUID.
sourceEventCountDecimal string for an integer from 1 to 4096.
matchedEventCountDecimal string no greater than sourceEventCount; equals events.length.
eventsArray of events, each with a client-ID eventId and decimal-string eventOrdinal.
deliveryModelive or replay.

eventOrdinal is zero-based in the source batch, strictly increases among delivered events, and remains below sourceEventCount. Filtering can leave gaps in ordinals and journal positions. frameSequence is a separate per-connection sequence.

When no events match, the server does not send an empty batch; a checkpoint can still advance the safe position. deliveryMode describes delivery, not event age or finality.

checkpoint and caught_up

checkpoint contains a position and the opaque token that the client can save for resume. Persist the token atomically with the effects it covers before sending ACK. caught_up contains only the cut position in addition to the common envelope.

These complete control frames show reaching cut 42; the token is synthetic and is not a usable credential:

{"version":1,"frameSequence":"3","kind":"checkpoint","position":"42","token":"EXAMPLE_NOT_A_VALID_CURSOR"}
{"version":1,"frameSequence":"4","kind":"caught_up","position":"42"}

A lost ACK does not undo the client’s durable checkpoint. Continue with batch processing and recovery.

reset_required and error

reset_required.code is cursor_expired or history_unavailable. The frame includes streamId, historyGeneration, retainedFloor (position or null), and exactly these permittedActions, in order: discover_current_history, explicit_live_cut. Reset is an explicit client decision; the server does not silently jump to the live frontier.

error contains a stable code and a boolean retryable. A reason string, message, or request ID is not part of this frame shape. Use the error action table to choose a response.