> ## Documentation Index
> Fetch the complete documentation index at: https://docs.foresight.now/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSocket

> Realtime CLOB feed: connect to /v1/ws, subscribe to public book/ticker/trades channels and the private user channel, and apply order-book deltas correctly.

The realtime gateway streams orderbook, ticker, trade, and private user events.

```
wss://api.foresight.now/v1/ws[?token=<ws_token>]
```

* The token is **optional** — without it you can still subscribe to public channels.
* The token is **required** for the private `user` channel. Get one from
  `POST /v1/auth/ws-token` (single-use, 60s TTL). See
  [Authentication](/foresight-apis/clob/authentication#websocket-tokens).

Every message — inbound and outbound — is JSON with a `type` field. Invalid JSON
gets `{ "type": "error", "message": "Invalid JSON" }`.

## Inbound messages (client → server)

### Ping

```json theme={null}
{ "type": "ping" }
```

The server replies `{ "type": "pong" }`. There is no server-initiated
heartbeat — send `ping` yourself periodically.

### Subscribe

Public channels require `condition_id` + `chain_id`:

```json theme={null}
{ "type": "subscribe", "channel": "book", "condition_id": "0x...", "chain_id": 56 }
```

The private channel requires only the channel name (events are wallet-scoped):

```json theme={null}
{ "type": "subscribe", "channel": "user" }
```

The server acks:

```json theme={null}
{ "type": "subscribed", "channel": "book", "condition_id": "0x...", "chain_id": 56 }
```

For the `book` channel, a `book_snapshot` is pushed immediately after the ack.

### Unsubscribe

Same shape as `subscribe`; the server acks with `type: "unsubscribed"`.

## Channels

### Public

| Channel  | Carries                                                |
| -------- | ------------------------------------------------------ |
| `book`   | Orderbook snapshot (once) + `book_delta_batch` updates |
| `ticker` | Best bid/ask and last-price updates                    |
| `trades` | Public trade prints                                    |

### Private

| Channel | Carries                                                                        |
| ------- | ------------------------------------------------------------------------------ |
| `user`  | Every order-lifecycle, fill, and settlement event for the authenticated wallet |

The `user` channel is a **single unified stream** — filter client-side on
`event.type`. Subscribing to it without a valid token returns:

```json theme={null}
{ "type": "error", "code": "AUTH_REQUIRED", "message": "Private channel requires authentication" }
```

<Info>
  One market `(condition_id, chain_id)` yields **three** public subscriptions:
  `book`, `ticker`, and `trades`. There is no wildcard subscription — subscribe
  per market.
</Info>

## Outbound messages (server → client)

### `book_snapshot`

Sent once after subscribing to `book`, and again on reconnect.

```json theme={null}
{
  "type": "book_snapshot",
  "condition_id": "0x...",
  "chain_id": 56,
  "seq": 42,
  "timestamp": 1713619200000,
  "bids": [{ "price": "0.54", "remainingSize": "123.45" }],
  "asks": [{ "price": "0.55", "remainingSize": "80.0" }]
}
```

Adopt `seq` as your sequence anchor. Each book level is
`{ price, remainingSize }` as decimal strings.

### `book_delta_batch`

A batch of price-level updates coalesced from a single matcher commit or cancel.
Apply all deltas as a group.

```json theme={null}
{
  "type": "book_delta_batch",
  "condition_id": "0x...",
  "chain_id": 56,
  "seq": 43,
  "deltas": [
    { "side": "BUY",  "price": "0.62", "size": "450" },
    { "side": "BUY",  "price": "0.61", "size": "0" },
    { "side": "SELL", "price": "0.64", "size": "120" }
  ]
}
```

| Field            | Meaning                                                                                     |
| ---------------- | ------------------------------------------------------------------------------------------- |
| `seq`            | Monotonic per `(condition_id, chain_id)`. Increments by 1 per batch. Use for gap detection. |
| `deltas[].side`  | `BUY` = bids, `SELL` = asks                                                                 |
| `deltas[].price` | Price level (decimal string, 2dp)                                                           |
| `deltas[].size`  | New **total** remaining size at that level. `0` means remove the level.                     |

<Warning>
  Read levels from `deltas[]`, not from the top-level message. Each entry's
  `size` is the new aggregate size at that price — replace the level, or delete
  it when `size` is `0`. The same `(side, price)` never appears twice in one
  batch.
</Warning>

### `ticker` / `trade`

```json theme={null}
{ "type": "ticker", "condition_id": "0x...", "chain_id": 56, "...": "..." }
{ "type": "trade",  "condition_id": "0x...", "chain_id": 56, "...": "..." }
```

### `user` events

The private `user` channel carries several event types — filter on `type`.

**Order-lifecycle events** carry the full order snapshot (same shape as
`GET /v1/orders/{hash}`):

| `type`               | Trigger                                                                                                      |
| -------------------- | ------------------------------------------------------------------------------------------------------------ |
| `order_placement`    | Order accepted and resting on the book                                                                       |
| `order_update`       | `remaining_size` changed (partial fill or settlement rollback)                                               |
| `order_cancellation` | You cancelled the order                                                                                      |
| `order_expired`      | Engine evicted it (no liquidity for MARKET, expired LIMIT, rollback)                                         |
| `order_filled`       | Fully matched — terminal `MATCHED`, then `FILLED` after settle                                               |
| `order_failed`       | Async matcher exhausted retries / hit a permanent error — `MATCH_FAILED` (may include `match_failed_reason`) |

```json theme={null}
{
  "type": "order_update",
  "order": {
    "order_hash": "0x...",
    "condition_id": "0x...",
    "chain_id": 56,
    "side": "BUY",
    "outcome": 1,
    "order_type": "LIMIT",
    "price": "0.55",
    "size": "100",
    "remaining_size": "60",
    "status": "PARTIALLY_FILLED",
    "token_id": "...",
    "fee_rate_bps": 100,
    "expiration": "0",
    "created_at": "...",
    "book_added_at": "...",
    "updated_at": "..."
  },
  "timestamp": 1713619200000
}
```

**Fill events** (`type: "fill"`) — one per match per side:

```json theme={null}
{
  "type": "fill",
  "order": { "...full order snapshot..." },
  "fill": { "batch_id": "...", "price": 0.55, "size": 50 },
  "trade_id": "...",
  "role": "maker",
  "timestamp": 1713619200000
}
```

**Settlement events** (`type: "settlement_update"`) — after on-chain settlement:

```json theme={null}
{
  "type": "settlement_update",
  "settlement_status": "SETTLED",
  "tx_hash": "0x...",
  "trade_ids": ["..."],
  "timestamp": 1713619200000
}
```

`FAILED` settlements may include `error_code` / `error_reason`.

### `error`

```json theme={null}
{ "type": "error", "code": "AUTH_REQUIRED", "message": "..." }
```

`code` is optional.

## Applying book deltas

<Steps>
  <Step title="Seed from the snapshot">
    Build `bids` and `asks` maps keyed by `price` from `book_snapshot`, using
    `{ price, remainingSize }`. Record `seq`.
  </Step>

  <Step title="Apply each batch atomically">
    For every entry in `book_delta_batch.deltas` (in array order): pick the side
    (`BUY` → bids, `SELL` → asks). If `size` is `0`, delete the level; otherwise
    set the level to `size`.
  </Step>

  <Step title="Detect gaps">
    Track `seq` per market. If a batch arrives with `seq !== last + 1`, you
    missed an update — resync by re-fetching `GET /v1/markets/{id}/book` or
    re-subscribing (which sends a fresh snapshot).
  </Step>

  <Step title="Render">
    Sort bids high→low, asks low→high, slice to the depth you need.
  </Step>
</Steps>

## Reconnect

1. `POST /v1/auth/ws-token` → fresh token (single-use).
2. Open `wss://api.foresight.now/v1/ws?token=<token>`.
3. Re-subscribe to every channel you need.
4. For `book`, the new `book_snapshot` resets your baseline — drop any deltas
   received before it.

<Info>
  A slow consumer is dropped if its send buffer backs up past \~1 MiB (close code
  `1013`). Reconnect, re-subscribe, and pull a fresh snapshot.
</Info>
