Skip to main content
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.
Every message — inbound and outbound — is JSON with a type field. Invalid JSON gets { "type": "error", "message": "Invalid JSON" }.

Inbound messages (client → server)

Ping

{ "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:
{ "type": "subscribe", "channel": "book", "condition_id": "0x...", "chain_id": 56 }
The private channel requires only the channel name (events are wallet-scoped):
{ "type": "subscribe", "channel": "user" }
The server acks:
{ "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

ChannelCarries
bookOrderbook snapshot (once) + book_delta_batch updates
tickerBest bid/ask and last-price updates
tradesPublic trade prints

Private

ChannelCarries
userEvery 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:
{ "type": "error", "code": "AUTH_REQUIRED", "message": "Private channel requires authentication" }
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.
{
  "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.
{
  "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" }
  ]
}
FieldMeaning
seqMonotonic per (condition_id, chain_id). Increments by 1 per batch. Use for gap detection.
deltas[].sideBUY = bids, SELL = asks
deltas[].pricePrice level (decimal string, 2dp)
deltas[].sizeNew total remaining size at that level. 0 means remove the level.
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.

ticker / trade

{ "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}):
typeTrigger
order_placementOrder accepted and resting on the book
order_updateremaining_size changed (partial fill or settlement rollback)
order_cancellationYou cancelled the order
order_expiredEngine evicted it (no liquidity for MARKET, expired LIMIT, rollback)
order_filledFully matched — terminal MATCHED, then FILLED after settle
order_failedAsync matcher exhausted retries / hit a permanent error — MATCH_FAILED (may include match_failed_reason)
{
  "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:
{
  "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:
{
  "type": "settlement_update",
  "settlement_status": "SETTLED",
  "tx_hash": "0x...",
  "trade_ids": ["..."],
  "timestamp": 1713619200000
}
FAILED settlements may include error_code / error_reason.

error

{ "type": "error", "code": "AUTH_REQUIRED", "message": "..." }
code is optional.

Applying book deltas

1

Seed from the snapshot

Build bids and asks maps keyed by price from book_snapshot, using { price, remainingSize }. Record seq.
2

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.
3

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).
4

Render

Sort bids high→low, asks low→high, slice to the depth you need.

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.
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.