Endpoint
Authentication
Browsers can’t set custom headers on the WebSocket handshake, so the signed-request flow moves to query params. The signature coversWS\nPATH\nSORTED_QUERY (excluding key_id/ts/sig)\nTIMESTAMP. See
Authentication for the full recipe.
Signed handshake
?access_token=<jwt>. Invalid or missing auth closes the connection with code 4401.
Protocol
All frames are JSON. Client frames are text; server frames are bytes (orjson-serialized UTF-8).Client → server
subscribe/unsubscribeaccept one or manyriver_ids. Duplicate subscribes on the same connection are no-ops.
Server → client
snapshotis sent once on successful subscribe when the orderbook is already cached. Subsequent changes are delivered asupdateframes with an identical payload shape.pendingis sent when the orderbook isn’t in cache yet. A snapshot arrives shortly, followed by updates.reconnectis sent during graceful pod shutdown. Reconnect with a small jitter.
Keepalive
Liveness is handled at the WebSocket protocol layer (RFC 6455 PING/PONG control frames). Standard clients (Pythonwebsockets, browser WebSocket) reply to server PINGs automatically — no application-level heartbeat is needed.
Orderbook payload
is_valid=false indicates a transient crossed-book state at the exchange. The snapshot that follows will have fresh values.
Rate limits and pacing
Orderbook updates are not rate-capped — every upstream change is forwarded as it happens. If your client falls behind, pending updates are coalesced per market (only the latest state is kept), so you never see stale data. A client that cannot drain fast enough is disconnected with code1011; reconnect and resubscribe to get a fresh snapshot.
Minimal client
Using the SDK (handles signing automatically):Close codes
| Code | Meaning |
|---|---|
1000 | Normal closure |
1011 | Server-side overflow (client couldn’t keep up with the send rate) |
4401 | Missing or invalid authentication |
4503 | Server draining for deploy — reconnect with jitter |

