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

Authentication

Browser clients

Browsers cannot set request headers on a WebSocket upgrade, so a first-party web client passes its Supabase JWT in the subprotocol list rather than the URL. Offer two values — the bare marker and the token-bearing one — and the server selects the marker:
A credential in a query string is written verbatim into every intermediary’s request log, which is why ?access_token= is no longer accepted. Programmatic clients are unaffected: the signed-handshake params are safe to log, because sig is bound to the path, query and timestamp and is single-use. 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

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):
Fill fields arrive at the top level of each frame and are readable as attributes (msg.exchange_trade_id, msg.qty, …). The subaccount is fixed at handshake time — there is no subscribe / unsubscribe on this stream; open one connection per subaccount. 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.

Close codes