- The token is optional — without it you can still subscribe to public channels.
- The token is required for the private
userchannel. Get one fromPOST /v1/auth/ws-token(single-use, 60s TTL). See Authentication.
type field. Invalid JSON
gets { "type": "error", "message": "Invalid JSON" }.
Inbound messages (client → server)
Ping
{ "type": "pong" }. There is no server-initiated
heartbeat — send ping yourself periodically.
Subscribe
Public channels requirecondition_id + chain_id:
book channel, a book_snapshot is pushed immediately after the ack.
Unsubscribe
Same shape assubscribe; 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 |
user channel is a single unified stream — filter client-side on
event.type. Subscribing to it without a valid token returns:
One market
(condition_id, chain_id) yields three public subscriptions:
book, ticker, and trades. There is no wildcard subscription — subscribe
per market.Outbound messages (server → client)
book_snapshot
Sent once after subscribing to book, and again on reconnect.
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.
| 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. |
ticker / trade
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) |
type: "fill") — one per match per side:
type: "settlement_update") — after on-chain settlement:
FAILED settlements may include error_code / error_reason.
error
code is optional.
Applying book deltas
Seed from the snapshot
Build
bids and asks maps keyed by price from book_snapshot, using
{ price, remainingSize }. Record seq.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.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).Reconnect
POST /v1/auth/ws-token→ fresh token (single-use).- Open
wss://api.foresight.now/v1/ws?token=<token>. - Re-subscribe to every channel you need.
- For
book, the newbook_snapshotresets your baseline — drop any deltas received before it.
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.