WebSocket frames v1
Exact application-frame names, types, and parser bounds.
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
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.
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:
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:
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:
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:
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:
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:
batch
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:
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.
