Skip to main content
Every server → client push uses the same envelope:
{
  "type":    "<event_type>",
  "sid":     12,
  "channel": "token_trades",
  "id":      "60550363974013526...",
  "data":    { ... }
}
FieldMeaning
typeConcrete event type (e.g. trade, book_snapshot, platform_status)
sidConnection-local subscription id you got from the subscribed response
channelOne of the channel names from the catalog
idThe token / condition / system id this event belongs to (omitted on user_activity since the channel is wallet-scoped)
dataEvent-specific payload (catalog below)
Use sid to route inside your client; use id to identify which specific subscribed entity matched.

user_activity (/ws/user, no id)

typedata
order_placedexchange payload
order_cancelledexchange payload
trade_matchedoff-chain match — exchange payload + recipient-side side. Trade is in DB as matched, not yet on-chain confirmed.
trade_fillon-chain settlement confirmed — { side: 'maker' | 'taker', orderHash, makerVault, takerVault, makerAssetId, takerAssetId, makerAmount, takerAmount, fee, blockNumber, txHash }. After this fires, /api/me/balances reflects the trade.
trade_failedsettlement-leg revert — { orderId, reason, reasonText, txHash }. Locked balance refunded; treat as never settled.
vault.deploy_confirmedauto-deploy job mined — { vaultAddress, txHash, blockNumber }
vault.deposit_confirmedchain.deposit indexed — { vaultAddress, token, amount, ledgerAfter, txHash }
user_paused{ user, untilBlock, txHash, blockNumber }
user_unpaused{ user, txHash, blockNumber }
trade_failed.reason is the raw 4-byte selector of the on-chain custom error. The most common is 0xc56873ba = OrderExpired() — see CTFExchange — MatchFailed reason codes.

vault_activity (/ws/user, ids = vault address[])

Same shape as user_activity but keyed by vault address, not authenticated wallet. Use it when a single API key needs to watch many vaults (e.g. multi_wallet partners with N sub-accounts, or custodial brokers managing multiple end-user vaults). id on each push is the lower-cased vault address. Required scope: portfolio:read. Subscription must include at least one vault id; subscribing without ids returns { code: 'invalid_params' }.
typedata
trade_fillSame payload as user_activity → trade_fill, but id is the lower-cased vault address. data.side is 'maker' or 'taker' from this vault’s perspective.
Other vault-affecting events (vault.deploy_confirmed, vault.deposit_confirmed) flow through user_activity only — they key by wallet, not vault.

all_trades (/ws/market, no id) — firehose

Public chain-confirmed trade firehose across every market. Same payload shape as token_trades → trade, but emitted regardless of which token the trade settled. No scope, no ids — every connected client receives every trade. Use when building a public ticker / volume scanner. Filter client-side by data.tokenId or data.marketSymbol. Note that high-volume markets can push hundreds of events/sec; scope your client buffers accordingly.

all_books (/ws/market, no id) — firehose

Public order-book delta firehose across every token. Same payload shape as token_book events, emitted regardless of token. No scope, no ids. Use sparingly — book deltas are the noisiest WS surface. Most partners are better served subscribing to token_book for the specific markets they trade.

token_trades (/ws/market, id = tokenId) ✅ live

typedata
trade{ tokenId, marketSymbol, outcomeIndex, tradeId?, price, qty, side, ts, txHash?, blockNumber? }
marketSymbol and outcomeIndex are emitted on every trade — partners who subscribe by tokenId but render by (marketSymbol, outcome) don’t need a separate lookup. tradeId is the matcher-assigned trade id; txHash / blockNumber appear once the trade settles on-chain (gateway receives them from chain-watcher). Live observed example:
{
  "type":    "trade",
  "sid":     12,
  "channel": "token_trades",
  "id":      "60550363974013526351721227761627460394700175914670861601864197814576471666136",
  "data": {
    "tokenId":      "60550363974013526351721227761627460394700175914670861601864197814576471666136",
    "marketSymbol": "NC26-BIN-79226633",
    "outcomeIndex": 0,
    "tradeId":      "t_01J3R7…",
    "side":         "buy",
    "qty":          "3.51",
    "price":        "0.67",
    "ts":           "2026-04-24T19:50:06.431Z"
  }
}
No backfill on subscribe — query GET /api/markets/{symbol}/trades for history.

token_book (/ws/market, id = tokenId) ⚠️ pending

typedata
book_snapshot{ tokenId, marketSymbol, outcomeIndex, bids, asks, seq, ts } — initial state on subscribe
book_update{ tokenId, marketSymbol, outcomeIndex, seq, prevSeq, bids, asks, ts }full top-of-book state at seq (not just the changed levels)
book_delta{ tokenId, marketSymbol, outcomeIndex, seq, prevSeq, changes, ts } — diff against prevSeq; derived from book_update
Both book_update and book_delta are emitted for every matcher book change — book_update is the raw full-depth replacement, book_delta is the computed diff. Most integrators want book_delta; book_update is there for clients that prefer to replay state wholesale without applying diffs. Subscribing to token_book will push both event types by default — filter on type client-side if you only want one. Subscription is accepted today, but neither book_snapshot nor book_delta is currently pushed by the gateway. Until the matcher → gateway book pipeline ships, poll GET /api/markets/{symbol}/orderbook?outcome=N&depth=20 on demand. When live: seq is monotonically increasing. After subscribe the server pushes exactly one book_snapshot immediately, then book_delta events where delta.prevSeq == previously_seen_seq. If your client sees a gap (delta.prevSeq != last_seen_seq), unsubscribe and resubscribe to force a fresh snapshot. See Reconnect — orderbook resync.

condition_status (/ws/market, id = conditionId) ⚠️ pending

typedata
market_paused{ conditionId, txHash?, blockNumber?, ts }
market_unpaused{ conditionId, txHash?, blockNumber?, ts }
market_resolved{ conditionId, resolution, payouts?, txHash?, blockNumber?, ts }
market_status{ conditionId, status, reason?, ts }
Subscription is accepted today; events not yet emitted by the upstream producer.

system (/ws/market, id = "platform_status") ✅ live

typedata
platform_statuspassthrough payload from the platform-status producer
{
  "type":    "platform_status",
  "sid":     15,
  "channel": "system",
  "id":      "platform_status",
  "data": {
    "frozen":      true,
    "reason":      "scheduled-maintenance",
    "since":       "2026-04-23T18:00:00Z",
    "etaUnfreeze": "2026-04-23T18:30:00Z"
  }
}