Skip to main content
PredictStreet exposes WebSocket streaming through two separate gateways that share one client protocol. Pick the gateway by what data you want; never mix subscriptions across the two on one socket. Both gateways authenticate with X-Api-Key — the same key you use on HTTP, same scope rules. The protocol is Kalshi-style (envelope with id / cmd / params) and Polymarket-style identifier lists (channel + ids[]).

Connect

Same handshake contract for both gateways:
Pick one of:
  • X-Api-Key: ps_live_<keyId>_<secret> header on the upgrade request
  • ?key=<token> query parameter — for browser clients that can’t set custom headers on the WebSocket upgrade
The server pushes one greeting after the upgrade:
On /ws/market the greeting is just { "gateway": "market", "authMethod": "api_key", "protocolVersion": 2 } — no wallet context, since market data isn’t per-user.
Auth is validated at handshake only. Rotating a key does not invalidate an open socket; close and reopen to switch identity. A bad key closes with 4401 <reason> (e.g. 4401 api_key_revoked). Revoking a key closes every live socket bound to that keyId immediately via apikey:invalidate Redis pub/sub.

Command envelope

Every client-to-server message uses the same shape:
  • id — client-side correlation id; echoed in the server’s response
  • cmdsubscribe / update_subscription / unsubscribe / list_subscriptions / ping
  • params — command-specific payload
Every accepted subscription gets a connection-local sid (numeric). Use sid for subsequent updates / unsubscribes / event routing on the client side. sids are not stable across reconnects.

Channel catalog

Public, on /ws/market: Private, on /ws/user: Detailed payloads + examples in Subscriptions and Messages. The two crypto feeds have their own page: Crypto price feeds.

Where do tokenId and conditionId come from?

Market data is keyed by on-chain native ids, not by app symbol. Pull them from REST first:
  • GET /api/markets/{symbol}conditionId / questionId / yesTokenId / noTokenId (binary)
  • For neg-risk multi-outcome markets, each question has its own conditionId and yes/no token ids — resolve them through the on-chain NegRiskAdapter before subscribing
Then open /ws/market and subscribe by those native ids:
Don’t subscribe by app symbol — symbols are app metadata, not the canonical on-chain identity.

What clients should NOT do

  • No symbol-based subscriptions. Use tokenId / conditionId.
  • No invented room strings like vault:0xabc:erc1155. Use channel + ids only.
  • No mixing of /ws/user and /ws/market on one socket. The gateway rejects cross-gateway channels with forbidden — open one socket per gateway.
  • No assumptions about sid stability across reconnects. After any close, re-subscribe from scratch.

Next

Subscriptions

Per-channel subscribe / update / unsubscribe payloads.

Message shapes

Outbound event envelope and full event catalog by channel.

Reconnect

Heartbeat, sid rebuild, snapshot resync.

API keys

Scope catalog, rotation, revoke.