WebSocket Protocol
Connect to the AGG WebSocket API to receive live aggregated orderbook updates, public trades, cross-venue arbitrage-return updates, JSON heartbeat events, and authenticated user events across connected venues.midpoint field on an orderbook frame is not a subscription.
- Wire
- SDK
The JSON frames on this page are the protocol.
Getting connected
1
Open a connection
Pass your The handshake must carry an
appId as a query parameter.Origin header that matches one of your app’s
allowed origins, or the connection closes with 4003.
Browsers send it automatically. A server or native client must set it explicitly.If the user is already signed in, you can include &token=eyJ... for immediate user-level
authentication. Sending the token with the authenticate message instead
keeps it out of URLs and logs.2
Wait for the connected message
The server acknowledges the connection with app and optional user context:
userId is present only when a valid JWT is supplied on connect. Send subscriptions after
this message arrives.3
Subscribe to outcome streams
Send one or more outcome IDs with a channel to start receiving orderbook or trade updates.
Each outcome has its own orderbook — there is no complement derivation.
outcomeId is an outcome’s id (outcomes[].id on REST market responses). Each outcome
belongs to one venue.Keep orderbook and trade subscriptions together at or under 500 per connection. Outcomes
beyond the limit are rejected with an
error message.1. Orderbook stream
Subscribe to an outcome’s orderbook to receive a full snapshot followed by incremental deltas. Each outcome has its own orderbook — there is no complement derivation. Levels carry venue attribution for the outcome’s venue, and the venue’s own book is included alongside.Subscribe
Initial snapshot
timestamp is Unix milliseconds on the wire, and the SDK reports it as seconds.
If no orderbook is available for an id, you get an error whose message starts with
Snapshot unavailable for <outcomeId> instead of a snapshot. The subscription stays active, so
deltas may still arrive: ignore deltas for an outcome you have no book for. Check that the id is
an outcome id (outcomes[].id), not a market id, then retry with resnapshot after a delay
rather than in a loop.
Incremental deltas
Market resolved
After you subscribe toorderbook for an outcome, a resolution or close arrives once as market_resolved. The outcome’s orderbook and trade subscriptions end with it: nothing further arrives, and you don’t need to unsubscribe. Trade-only subscribers are not sent this message. result is the label of the first outcome with winner: true, or null when none is marked winner. outcomes[] are the sibling outcomes for that market.
status is resolved or closed. outcomes can be empty, and winner is omitted when it isn’t known. payoutPerShare and refundAtCost are omitted when the venue did not report them. Settlement meaning is on Market resolution.
Sequencing and resync
Every orderbook message includes aseq value for that outcome. seq is monotonic within one
stream of the book. Your client is responsible for maintaining local book state and detecting
integrity issues.
Apply deltas when delta.seq === book.seq + 1. After applying, recompute the checksum
(XOR of per-level CRC32 values) and compare it to checksum in the message. If they match, the
book is consistent. The exact level encoding is in the
market-data guide.
Drop stale deltas where delta.seq <= book.seq — these can arrive after a subscribe when
deltas predate the initial snapshot. Silently discard them.
Request a resnapshot if you detect a forward gap (delta.seq > book.seq + 1) or a checksum
mismatch:
orderbook_snapshot, even when its seq is lower than
yours. If AGG rebuilds an outcome’s book, that book starts a new seq stream. When the first
update from the rebuilt book arrives, the gateway sends you a fresh orderbook_snapshot without
being asked. Apply the following deltas against the snapshot’s seq.
Unsubscribe
Staleness checks
Heartbeats prove the connection is alive, not that each outcome is updating. A quiet market can legitimately go several minutes without a delta, so silence alone is not an error, and silence alone can’t tell you a stream has stopped reaching you. We recommend a 5-minute REST watchdog for every client that keeps books open. The gateway can only resend a snapshot when it sees a new update, so a quiet book that went wrong stays wrong. The watchdog confirms each book against the server instead of trusting that the stream is still flowing.- Once a minute, find subscribed outcomes whose book has not changed for 5 minutes.
- Fetch up to 100 of them with
GET /orderbooks/outcomes?venueMarketOutcomeIds=id1,id2,…&depth=1(see Market-data API). Results come back in request order, withnullfor an id that can’t be served. - Compare each returned
seqwith your book’sseq:- Same: the market is quiet and your book is current. Reset its 5-minute timer.
- Different: you missed updates. Send
resnapshotfor that outcome. - Still different on the next check:
unsubscribeandsubscribethe outcome. That resets all per-subscription state on the gateway.
- Ignore an outcome whose book changed while the request was in flight. Its
seqmoved on for a normal reason.
429, wait for Retry-After before the next check.
Best practices
Connection- Wait for
connectedbefore you subscribe. Only the first 20 messages sent before it are kept; the rest are dropped. - Answer protocol pings. Browsers and most WebSocket libraries do this automatically; a connection that misses two pings in a row is closed.
- Treat 45 seconds without any frame, heartbeats included, as a dead connection and reconnect. Heartbeats arrive every 30 seconds.
- Reconnect with exponential backoff and jitter. After reconnecting, discard your books, subscribe again to everything, and rebuild from the new snapshots. Subscriptions don’t survive a new connection.
- Don’t keep reconnecting on
4001or4003. Those mean a missing app id, an inactive or unknown app, a disallowed origin, or a non-user token, and retrying won’t help until the configuration is fixed.
- Apply a delta only when its
seqis exactly one more than your book’s, and verify itschecksum. - On a forward
seqgap or a checksum mismatch, sendresnapshot. Never keep patching a book you can’t verify. - Replace your book on every
orderbook_snapshot, whatever itsseq. - Run the 5-minute REST watchdog.
- On
market_resolved, drop that outcome’s book.
- Subscribe only to outcomes you are displaying or trading, and unsubscribe when you stop.
- Keep your message handler fast and move heavy work off it. If your client falls more than
about 1 MB behind, the gateway skips frames for it. On orderbooks this shows up as a
seqgap, which a resnapshot repairs. Skipped trades, heartbeats, user events, arbitrage updates andmarket_resolvedmessages are not resent.
- Prefer the
authenticatemessage to atokenin the URL. - A token is checked when you send it. After a refresh, use the new token on your next
authenticateor reconnect, so an expired token never downgrades a reconnect to app level. - Send
x-app-api-key(for example on the watchdog request) only from your backend, never from a browser.
2. Trade stream
Subscribe to the public trade feed for an outcome. No user auth is required.Subscribe
Trade event
Unsubscribe
3. Authenticated user events
Receive user-specific events, such as order confirmations and balance updates, after the connection has user-level auth.Authenticate
- On connect
- Mid-session
Include the JWT in the connection URL:
Order submitted
Balance update
Order event
Venue-agnostic stream of execution-plan lifecycle and terminal order status events. Emitted for every active venue (Polymarket, Predict, Opinion, …) automatically — no per-venue handling on the client. Discriminate on theevent field.
Step lifecycle (progress UI)
event values: step_started | step_completed | step_waiting | step_failed | step_retrying.
sequence is the 1-based declaration index of the step within the execution plan, and totalSteps is the total step count. Pair them to render progress like “Step 3 of 7”. dag_started also carries totalSteps so the UI can show an empty progress bar before the first step runs. For branching execution plans, sequence reflects declaration order, not strict execution order — parallel branches may emit non-monotonic sequence values.
Terminal fill (success toast + position refresh)
event values: filled | partial_fill. remainingAmountRaw is present only on partial_fill.
Failure (error toast)
event values: failed (order-level) | dag_failed (execution-plan-level).
Execution plan lifecycle (optional)
event values: dag_started | dag_completed | dag_failed | dag_cancelled.
All
order_event messages are broadcast verbatim — the gateway does not filter by event type. Switch on event and ignore types your UI doesn’t care about.redeem_event reports EVM redemption completion. See Market resolution
for how to track claims and update positions.
For a notification-focused recipe, see User Notifications. That page does not repeat these frames.
4. Arbitrage streams
Stream the live cross-venue arbitrage return (arbReturn) for matched markets — the estimated
return, as a decimal fraction (e.g. 0.012 = 1.2%), from the best prices across the venues in a
matched cluster. This is the same value returned by the
List Venue Events API; the stream keeps it live.
There are two ways to consume it: a per-market channel for a focused view (one or a few
markets), and a single coalesced feed channel for a whole listing.
Arbitrage streams are delta-only — there is no snapshot or sequence number. Load the initial
arbReturn from a REST listing, then apply stream updates on top. Updates are last-value-wins
and loss-tolerant: a dropped message self-heals on the next update. Subscribing requires only an
app-level connection (your appId) — arbReturn is not user-scoped. For REST request and
response details, use the API Reference.Per-market channel
Subscribe with one or moremarketIds (a market’s id) to receive an update whenever that
market’s arbitrage return moves.
outcomeIds contains the marketIds you subscribed:
activeVenuesOnly is always true: the value only reflects active venues.
venueEventId is the parent event of the market (or null), so you can roll updates up to an
event-level value (e.g. the max across an event’s markets).
Unsubscribe:
Feed channel
For a listing of many markets, subscribe once to the coalesced feed instead of managing a subscription per visible market. No ids are sent.arbReturn changed since the last flush (deduped to the latest value per market):
Connection management
Heartbeat
The service emits a JSON heartbeat event that clients can observe directly:heartbeat (or any frame) as your liveness signal:
if nothing arrives for 45 seconds, reconnect.
A connection that doesn’t answer two pings in a row is closed by the server.
Reconnection
If the connection closes, reconnect with exponential backoff and jitter. Once the new connection sendsconnected, discard your books and subscribe again to everything:
Error handling
Protocol or validation errors arrive as JSON messages:token on connect also leaves it open: the connection continues at app level and you
receive an error explaining why.
Connection-level failures send an error and then close with one of these codes:
Related
Setup Guide
Provider wiring, auth setup, and SDK client initialization.
Real-Time Orderbook
SDK, hooks, and UI orderbook implementations.
Real-Time Charts
Build live candles from REST history plus WebSocket events.
Real-Time Arbitrage
Live cross-venue arbitrage returns via per-market and feed channels.
User Notifications
Authenticated order and balance events with refresh-aware reconnect logic.