Skip to main content

Endpoint

A single WebSocket connection can subscribe to any number of markets. The server pushes one frame per trade print, in real time. There is no snapshot on subscribe — see Backfill.

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. See Authentication for the full recipe.
The official SDKs sign the handshake for you.
Signed handshake
For first-party web clients, authenticate with a Supabase JWT carried in the subprotocol list — browsers cannot set headers on a WS upgrade, and a token in the query string would be written into every intermediary’s request log:
The server selects the bare river.jwt marker, so the token never appears in a response either. Invalid or missing auth closes the connection with code 4401.

Backfill

The WebSocket stream is live-only — it does not emit historical trades on subscribe. To populate a recent trades view on page load, call:
This REST endpoint accepts one or more river_ids (repeat the parameter), queries each source exchange sequentially, and returns the most recent trades for every market in descending time order. The response is { "results": [...] } with one block per requested river_id in input order. Per-market failures show up as not_found rows with a message. Fire it in parallel with the WebSocket subscribe action and deduplicate overlap by exchange_trade_id.

Protocol

All frames are JSON. Client frames are text; server frames are bytes (orjson-serialized UTF-8).

Client → server

  • subscribe / unsubscribe accept one or many river_ids. Duplicate subscribes on the same connection are no-ops.

Server → client

  • trade is sent once per print. Append-only — frames are never coalesced.
  • reconnect is sent during graceful pod shutdown. Reconnect with a small jitter.

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.

Tradeprint payload

Rate limits and pacing

Trades are append-only events — they are not rate-capped or coalesced. Every print on the upstream exchange is forwarded. If a slow client cannot drain fast enough, the server closes the connection with code 1011; reconnect and re-backfill.

Minimal client

Using the SDK (handles REST + WS signing automatically):
msg.type and top-level frame fields (msg.river_id) are attributes; msg.data is a plain dict — index it.

Changing subscriptions mid-stream

The object returned by client.realtime.tradeprints(...) exposes subscribe / unsubscribe. Both accept one or many river_ids and can be called at any time — typically from a separate task while the async for loop drains messages. The SDK tracks the active set, so after a transparent reconnect the current set (including mid-stream changes) is re-subscribed automatically.

Close codes