Skip to main content

Endpoint

Each connection is scoped to exactly one subaccount. To watch fills across multiple subaccounts, open one connection per subaccount. The server pushes one frame per execution as fills arrive. Unlike the orders stream — which carries only cumulative per-order totals (traded_qty, average_price, fees_paid) — this stream delivers each discrete execution with its true exchange_trade_id, price, qty, fee, maker/taker flag, and counterparty.

Query parameters

ParameterRequiredDefaultDescription
subaccount_idyesUUID of the subaccount whose fills you want to stream. Must belong to the authenticated user.
key_id / ts / sigyes (programmatic)Ed25519 signed-handshake params. See Authentication.
access_tokenyes (browser)Supabase JWT for first-party web clients.

Authentication

Browsers can’t set custom headers on the WebSocket handshake, so the signed-request flow moves to query params. The signature covers WS\nPATH\nSORTED_QUERY (excluding key_id/ts/sig)\nTIMESTAMP.
The official SDKs sign the handshake for you. The raw recipe below is for integrations without an SDK.
Signed handshake
The authenticated user must own subaccount_id — otherwise the connection is closed with code 4401. A missing or malformed subaccount_id is closed with 4400.

Protocol

All frames are JSON. Server frames are bytes (orjson-serialized UTF-8). Client frames are accepted but ignored — there is no subscribe/unsubscribe action; the subaccount is fixed at handshake time. This is a pure live-delta stream — there is no snapshot frame. Backfill history with GET /v1/fills and dedup any overlap against the live stream by the composite fill identity: (exchange_trade_id, exchange_order_id, counterparty_address, counterparty_exchange_order_id). exchange_trade_id alone is not unique — one Polymarket trade produces one fill per matched maker order.

Server → client

  • connected is sent immediately after the handshake completes.
  • reconnect is sent during graceful pod shutdown. Reconnect with a small jitter.

Fill payload

FieldDescription
river_idInstrument the fill occurred on.
river_order_idInternal id of the order this fill belongs to. Links a fill back to the orders stream’s id.
client_order_idYour client-supplied order id, if one was set.
exchange_order_idExchange-side order id.
exchange_trade_idExchange-side trade id. Not unique on its own: a multi-maker trade shares one trade id across several fills.
exchange_timestampExecution time reported by the exchange (UTC).
priceExecution price of this fill.
qtyExecuted quantity of this fill.
feeFee charged for this execution, in the settlement currency.
buy_flagtrue if this was a buy.
is_makertrue if you provided liquidity (maker), false if you took it (taker).
counterparty_addressOn-chain counterparty address where applicable, else null.
counterparty_exchange_order_idExchange-side id of the counterparty’s order where applicable, else null.

Keepalive

Liveness is handled at the WebSocket protocol layer (RFC 6455 PING/PONG control frames). Standard clients (Python websockets, browser WebSocket) reply to server PINGs automatically — no application-level heartbeat is needed.

Minimal client

Using the SDK (handles signing automatically):
Or signing the handshake manually:

Errors

Recoverable problems arrive as an error frame followed by a close. code is stable and safe to branch on; message is human-readable.
codeWhen
unauthorizedAPI key / JWT was missing or invalid.
missing_subaccount_id?subaccount_id= was not supplied on the handshake.
invalid_subaccount_idsubaccount_id is not a valid UUID.
subaccount_forbiddenThe subaccount does not belong to the authenticated user.

Close codes

CodeMeaning
1000Normal closure
1011Server-side overflow (client couldn’t keep up with the send rate)
4400Bad request (missing/invalid subaccount_id)
4401Missing or invalid authentication, or subaccount does not belong to the user
4503Server draining for deploy — reconnect with jitter