> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.whalehunt.net/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.whalehunt.net/_mcp/server.

# WebSocket frames v1

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](/polymarket/reference/events).

## Common encoding rules

| Value                         | Wire representation and bound                                                                                                                                                                                      |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `version`                     | JSON integer. The current version is `1`.                                                                                                                                                                          |
| `frameSequence`               | Canonical decimal string from `1` through `u64::MAX`, allocated per connection.                                                                                                                                    |
| Position                      | Canonical decimal string from `0` through `i64::MAX`. No sign or leading zeroes.                                                                                                                                   |
| Generation                    | Canonical decimal string from `1` through `u64::MAX`. No sign or leading zeroes.                                                                                                                                   |
| UUID                          | A 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`. |
| Hash32                        | Serializer output is canonical lowercase `0x` followed by 64 lowercase hexadecimal characters.                                                                                                                     |
| Client ID                     | 1–128 ASCII characters: letters, digits, `.`, `:`, `_`, or `-`.                                                                                                                                                    |
| Ticket or opaque resume token | 1–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.

| `kind`         | Required fields                                                         | Purpose                                                                   |
| -------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| `authenticate` | `ticket`                                                                | Present the operator-issued API key for the approved account.             |
| `subscribe`    | `requestId`, `consumerId`, `subscriptionId`, `filters`, `start`         | Create a subscription or resume one using its saved token.                |
| `rebind`       | `requestId`, `subscriptionId`, `expectedGeneration`, `filters`, `start` | Explicitly change the filter binding for an existing subscription.        |
| `ack`          | `token`, `connectionId`, `generation`                                   | Acknowledge 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:

```json
{"mode":"live_cut"}
```

```json
{"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:

```jsonl
{"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:

| Field                   | Meaning                                           |
| ----------------------- | ------------------------------------------------- |
| `authTimeoutMs`         | Authentication deadline in milliseconds.          |
| `subscribeTimeoutMs`    | Subscribe deadline in milliseconds.               |
| `sendTimeoutMs`         | Send deadline in milliseconds.                    |
| `maxPageBatches`        | Maximum batches in a replay page.                 |
| `maxPageBytes`          | Maximum replay-page bytes.                        |
| `replayLeaseTtlMs`      | Replay lease time-to-live in milliseconds.        |
| `replayLeaseByteBudget` | Replay lease byte budget.                         |
| `maxUnackedCount`       | Maximum unacknowledged checkpoints.               |
| `maxUnackedBytes`       | Maximum unacknowledged bytes.                     |
| `maxUnackedAgeMs`       | Maximum unacknowledged age in milliseconds.       |
| `maxClientFrameBytes`   | Maximum client application frame: 262144 bytes.   |
| `maxServerFrameBytes`   | Maximum 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:

| Field           | Wire type and rule                                                                             |
| --------------- | ---------------------------------------------------------------------------------------------- |
| `sourceScope`   | 1–128 ASCII characters: lowercase letters, digits, `.`, `:`, `_`, or `-`.                      |
| `network`       | The string `polygon:137`.                                                                      |
| `contractScope` | 1–128 ASCII characters using the same grammar as `sourceScope`.                                |
| `capability`    | `unknown`, `ready`, or `gap`.                                                                  |
| `lastPosition`  | `null` 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:

| Field                           | Wire type and rule                                                                                                                                               |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `version`                       | JSON integer `1`.                                                                                                                                                |
| `network`                       | `NetworkRef`: `namespace` is `eip155`, `chain_id` is JSON integer `137`, and `genesis_hash` is canonical `Hash32`. This differs from the `scope.network` string. |
| `publication.generation`        | Non-nil UUID.                                                                                                                                                    |
| `publication.revision`          | Positive `u64` decimal string.                                                                                                                                   |
| `publication.manifestHash`      | Canonical `Hash32`.                                                                                                                                              |
| `publication.manifestAlgorithm` | 1–64 ASCII alphanumeric, `.`, `-`, or `_` characters.                                                                                                            |
| `publication.publishedAt`       | RFC 3339 timestamp, at most 64 characters.                                                                                                                       |
| `markets`                       | 1–256 selected catalog rows, sorted by slug in the canonical snapshot.                                                                                           |
| `markets[].marketId`            | 1–64 ASCII digits, no leading zero.                                                                                                                              |
| `markets[].slug`                | 1–256 lowercase ASCII letters, digits, or hyphens; no leading or trailing hyphen.                                                                                |
| `markets[].mappingRevision`     | Positive `u64` decimal string.                                                                                                                                   |
| `markets[].conditionId`         | Nonzero canonical `Hash32`.                                                                                                                                      |
| `markets[].tokenIds`            | 1–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:

```json
{
  "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`

| Field               | Wire type and rule                                                                  |
| ------------------- | ----------------------------------------------------------------------------------- |
| `position`          | Position string for the atomic journal batch.                                       |
| `batchId`           | Non-nil UUID.                                                                       |
| `sourceEventCount`  | Decimal string for an integer from 1 to 4096.                                       |
| `matchedEventCount` | Decimal string no greater than `sourceEventCount`; equals `events.length`.          |
| `events`            | Array of events, each with a client-ID `eventId` and decimal-string `eventOrdinal`. |
| `deliveryMode`      | `live` 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:

```jsonl
{"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](/polymarket/stream/processing) and [recovery](/polymarket/stream/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](/polymarket/reference/errors) to choose a response.