Subscribe
Each gateway is a separate socket with its ownsid namespace.
Subscribe per gateway.
/ws/user — private
/ws/market — public
sids are connection-local — each gateway numbers from 1
independently, so a sid: 10 on the user socket and a sid: 10 on
the market socket point at completely different subscriptions.
Trying to subscribe to the wrong-gateway channel comes back as
{ "code": "forbidden" } in rejected[].
Normalization rules
- All ids are trimmed and lowercased (addresses,
conditionIds, market symbols, instrument ids). tokenIdmust be a canonical decimal string (no leading zeroes, no hex); anything else is rejected withinvalid_params. There is no*wildcard — to receive every token, use theall_*channels (see Public — platform-wide below).conditionIdis lowercased 32-byte hex.market_price_tickids are<symbol>:<outcomeIndex>, with the outcome index written without leading zeros (0–255).- Channels that take no ids (
user_orders,user_fills,user_activity,notifications, theall_*channels,global_activity) reject a subscription that sends any. - Duplicate ids removed.
Rejection codes
A subscription that fails validation comes back underrejected[]:
message field redundantly prefixes the code — that’s an
artifact of the gateway, not a separate signal. Route by code;
treat message as the human-readable detail.
Per-channel reference
Private — own activity (typed)
user_orders — own order lifecycle
- Gateway:
/ws/user ids: not allowed (always scoped to the socket’s wallet)- Required scope:
portfolio:read - Pushes:
order_placed,order_cancelled,order_rejected,order_expired,trade_failed
user_fills — own fills at match time
- Gateway:
/ws/user ids: not allowed (always scoped to the socket’s wallet)- Required scope:
portfolio:read - Pushes:
user_fill(source: "matcher") when the matcher fills one of your orders, before on-chain settlement. UseGET /api/me/tradesfor the settled view with fees.
vault_positions — per-vault balance / split / merge / redeem
- Gateway:
/ws/user ids: your vault address (lowercased) — the vault of the wallet this socket is bound to- Required scope:
portfolio:read - Pushes:
vault_position_balance_changed,vault_position_split,vault_positions_merged,vault_position_redeemed
vaultAddress in the connected greeting). Any other vault
comes back under rejected[] with forbidden, and so does every
vault_positions subscribe on a socket that connected before the
wallet’s vault was deployed — reconnect once the vault exists.
vault_activity — on-chain vault events
- Gateway:
/ws/user ids: your vault address (lowercased); any other vault isforbidden- Required scope:
portfolio:read - Pushes:
trade_fill(an on-chainOrderFillednaming your vault, withtxHashandfee),deposit_confirmed,withdrawal_confirmed,exchange_balance_synced, and the vault’s other on-chain events
user_fills. Payloads, and how trade_fill
reports maker and taker orders, are in
Server events.
user_activity — withdrawals and account events
- Gateway:
/ws/user ids: not allowed (always scoped to the socket’s wallet)- Required scope:
portfolio:read - Pushes:
withdrawal_<state>on each status change of your withdrawals,vault_created,user_paused/user_unpaused
Private — notifications and account signals
notifications — in-app notification feed
- Gateway:
/ws/user ids: not allowed (always scoped to the socket’s wallet)- Required scope:
portfolio:read - Pushes:
notification— one per notification added to the wallet’s web-app notification feed
me_limits_invalidated — deposit-limit changes
- Gateway:
/ws/user ids: API key — exactly your wallet (the greeting’swalletAddress); JWT session — none- Required scope:
portfolio:read - Pushes:
me_limits_invalidatedwhen an on-chain change to the wallet’s deposit tier, caps or deposit / trading / withdrawal restriction is indexed
GET /api/me/deposit-limits
when it arrives, instead of polling.
bond_payout_credited — refunded challenge bonds
- Gateway:
/ws/user ids: API key — exactly your wallet; JWT session — none- Required scope:
portfolio:read - Pushes:
bond_payout_creditedwhen the bond of a challenge you raised is credited back — plus the reward when the proposal is overridden, the bond alone when the market is voided or the proposal cleared — and becomes claimable from the oracle
dispute_intent — disputes filed from the web app
- Gateway:
/ws/user ids: API key — exactly your wallet; JWT session — none- Required scope:
portfolio:read - Pushes:
dispute_intent_submitted,dispute_intent_accepted,dispute_intent_rejected,dispute_intent_expired
ids is rejected with invalid_params, any other wallet with
forbidden. The id is case-insensitive, and the ack lists the
subscription without ids.
Public — trades
token_trade_matches — low-latency matcher tape
- Gateway:
/ws/market ids: one or more outcometokenIds (decimal-string)- Pushes:
trade_matched(source: "matcher") per off-chain match
token_trade_settlements — chain-confirmed settlements
- Gateway:
/ws/market ids: one or more outcometokenIds- Pushes:
trade_settled(source: "chain") on indexedOrderFilled
txHash,
blockNumber, and orderHash.
Public — books
token_book — public orderbook snapshots + updates
- Gateway:
/ws/market ids: one or more outcometokenIds- Pushes:
book_snapshot(initial),book_update(the whole book: all price levels on both sides),book_delta(diff againstprevSeq),book_snapshot_failed(snapshot fetch couldn’t complete)
book_update is the whole book, not top-of-book. Each frame carries
every resting price level on both sides, aggregated per price (price, total
quantity, order count), as the book stands at seq. Overwrite your local
book with it rather than patching; book_delta is the incremental form.
Levels below the dust threshold are removed, the same filter the REST book
uses.Depth: the initial book_snapshot (and get_book_snapshot) is capped at
100 levels per side, and so is a periodic recovery frame, sent as a
book_update about every 5 minutes. Prices sit on a 0.01 grid, so a side
never has more than 99 levels and neither cap cuts the book: every one of
these frames carries the whole book.book_snapshot (depth 100),
then book_update events with monotonically increasing seq.
book_delta is emitted only when the gateway has contiguous local
book state — if seq or prevSeq is missing on a producer frame the
gateway drops the live book_update rather than emit a divergent
delta.
See Reconnect — orderbook resync
for the gap-detection flow built around book_delta.prevSeq.
token_ohlc — rolling 5-second candles
- Gateway:
/ws/market ids: one or more outcometokenIds- Pushes:
ohlc_update(interval: "s5") — fires whenever the matcher prints inside the active 5-second window
isClosed: true.
market_price_tick — live probability chart
- Gateway:
/ws/market ids: one or more<symbol>:<outcomeIndex>pairs — market symbol (case-insensitive, lower-cased in the ack) and 0-based outcome index- Pushes:
price_tick— the outcome’s mark (the market’schancesvalue) for the current second
GET /api/markets/{symbol}/price-history. At most
about one frame per second per outcome, only while the market is
OPEN and only when the outcome’s best bid, best ask or last trade
changed — a quiet market is silent. No backfill: seed the chart from
GET /api/markets/{symbol}/price-history?range=live.
Public — platform-wide
These channels take noids and deliver events for every market on
the platform. Each counts as one subscription.
all_trade_matches — every matcher trade
- Gateway:
/ws/market ids: not allowed- Pushes:
trade_matched, the same frames astoken_trade_matchesfor every token — two per binary-market trade (the NO mirror)
all_trade_settlements — every chain settlement
- Gateway:
/ws/market ids: not allowed- Pushes:
trade_settled, the same frames astoken_trade_settlementsfor every token
all_books — every orderbook change
- Gateway:
/ws/market ids: not allowed- Pushes:
book_updateandbook_delta, the same frames (sameseqandhash) astoken_bookfor every token; nobook_snapshoton subscribe
global_activity — platform-wide trade feed
- Gateway:
/ws/market ids: not allowed- Pushes:
global_activity— one frame per matched trade, in the taker’s view, with market / event titles and the taker’s username when available
all_trade_matches: one frame
per trade, no mirror to dedupe. Seed it from GET /api/activity/recent,
which serves the same fields. Payloads for all four channels:
Server events.
Public — lifecycle
condition_lifecycle — on-chain trading pause
- Gateway:
/ws/market ids: one or moreconditionIds (lowercased bytes32 hex)- Pushes:
condition_trading_paused,condition_trading_unpaused
{ conditionId, pauser, txHash, blockNumber, tsMs }. Status
transitions, disputes and resolution are keyed by market symbol:
subscribe to market_lifecycle (below) for those.
market_lifecycle — per-market lifecycle
- Gateway:
/ws/market ids: one or more marketsymbols (lower-cased in the ack)- Pushes:
status_changed,market_disputed,market_resolved, and — on a crypto Up/Down window —crypto_window_opened/crypto_window_closed
symbol rather than the on-chain conditionId, so
one subscription per market page carries every market-scoped
transition. Optional fields are omitted when absent, and the frames are
a hint, not the record — re-fetch GET /api/markets/{slug} for
authoritative state.
Public — crypto price
crypto_price_tick — live underlying-asset price
- Gateway:
/ws/market ids: one or more cryptoinstrumentIds (BTC-USD,ETH-USD) — case-insensitive, lower-cased in the ack- Pushes:
crypto_price_tick
price is an unscaled integer string plus a decimals
exponent; no backfill on subscribe, and silence simply means no
producer is active for that instrument. Full contract:
Crypto price feeds.
system — platform-wide status
- Gateway:
/ws/market ids: must be["platform_status"]
GET /api/platform/status
({ frozen, reason, since, activeNotices }).
Resource limits
Per-connection caps. Exceeding one surfaces as a structured error, except the outbound buffer, where the connection is dropped:
The id limit applies to the subscription’s whole id set:
add_ids
fails once the ids already on the sid plus the new ones (counted
before duplicates are removed) exceed 100, and the subscription is left
unchanged.
The command window is fixed, not sliding: it starts with a command and
lasts 60 s, and the first command after it starts the next one. Every
command counts, ping included.
The values above are the gateway defaults; the command rate is also
the production value.
Update existing subscription
For id-based channels (token_trade_matches, token_trade_settlements,
token_book, token_ohlc, market_price_tick, condition_lifecycle,
market_lifecycle, crypto_price_tick, vault_positions,
vault_activity, system) you can change ids on an existing sid
instead of opening a new subscription. The other channels
(user_orders, user_fills, user_activity, notifications,
dispute_intent, bond_payout_credited, me_limits_invalidated, the
all_* channels and global_activity) have no editable id list, so
update_subscription returns invalid_params for them.
Add ids
Remove ids
Snapshot refresh: get_book_snapshot
The get_book_snapshot command lets a token_book subscriber
request a fresh snapshot without unsubscribing /
resubscribing. Useful when a sequence gap is detected
(book_delta.prevSeq != lastSeq).
sid:
sid must be a token_book subscription; add ids next to it to
refresh only some of its tokens. The server acknowledges with
{ "id": 7, "type": "ok", "channel": "token_book", "sid": 12, "ids": ["12345..."] }
(sid only when you passed one), then pushes a book_snapshot per
token id, or book_snapshot_failed for a token whose snapshot fetch
failed:
retryable is always true — back off and request again.
subscriptionActive is true when the request carried a sid (that
subscription stays in place and keeps streaming) and false for a
request by ids alone.
Unsubscribe
List subscriptions
Ping
ts is the server clock in milliseconds — clients can use it as a
clock-skew probe.