Skip to main content

Overview

Struct’s websocket API streams real-time Polymarket data directly to your application without polling REST. Connect once, subscribe to any number of rooms, and receive live updates for trades, prices, metrics, positions, PnL, order books, and more. Websockets are ideal for trading UIs, dashboards, and agents that need low-latency access to what’s happening on Polymarket right now. Every metric stream is backed by the same pre-computed materialised rows the REST API serves, so push values are consistent with what you’d pull on demand.

Endpoints

There are two websocket endpoints:
EndpointPurpose
wss://api.struct.to/wsRooms: subscribe to data streams by topic (trades, prices, metrics).
wss://api.struct.to/ws/alertsAlerts: webhook-style events pushed over the same connection.
This guide covers the Rooms endpoint. See Alerts for the alerts endpoint.

Authentication

Authenticate by appending your API key as a query parameter:
wss://api.struct.to/ws?api-key=YOUR_API_KEY
For browser or mobile clients, use a JWT public key and add a token parameter. See Authentication for both flows.

Your first connection

1

Create an account

Sign up at struct.to/dashboard and create an organisation.
2

Generate an API key

Open the API Keys page in your dashboard and create a new key. Copy the value somewhere safe; you won’t be able to view it again. See Authentication for key types, JWT public keys, and rotation.
3

Open a connection

Use any WebSocket client to connect. The example below connects, subscribes to the Trades room for a specific market, and logs messages as they arrive.
import { StructWebSocket } from "@structbuild/sdk";

const ws = new StructWebSocket({ apiKey: "YOUR_API_KEY" });
await ws.connect();
await ws.subscribe("polymarket_trades", { condition_ids: ["0xabc..."] });

ws.on("trade_stream_update", (event) => {
  console.log(event.condition_id, event.price, event.side);
});
4

Receive events

Messages arrive as JSON with a type field (the event name) and a message payload. See the individual room pages for the events each room emits.

Connection lifecycle

A typical session follows the same sequence for every room:

Message protocol

All messages sent and received are JSON. Client messages use a type and payload envelope:
{
  "type": "join_room",
  "payload": { "room_id": "polymarket_trades" }
}

Joining and subscribing

Subscribing to a room is a two-step flow:
  1. Join the room with join_room.
  2. Configure the subscription by sending a room_message with action: "subscribe" and any filters.
{
  "type": "room_message",
  "payload": {
    "room_id": "polymarket_trades",
    "message": {
      "action": "subscribe",
      "condition_ids": ["0xabc..."],
      "market_slugs": ["will-x-happen"]
    }
  }
}
You can update a subscription at any time by sending another subscribe message. The new filters replace the previous ones. Subscribe server-side rather than filter client-side: server-side filtering is free, while client-side filtering still bills you for the message.

Unsubscribing

To stop receiving messages from a room, send unsubscribe_all followed by leave_room:
{
  "type": "room_message",
  "payload": {
    "room_id": "polymarket_trades",
    "message": { "action": "unsubscribe_all" }
  }
}
{
  "type": "leave_room",
  "payload": { "room_id": "polymarket_trades" }
}

Keepalive

Send a ping every 30 seconds to keep the connection alive:
{ "type": "ping" }
The server responds with { "type": "pong" }, which you can safely ignore. The TypeScript SDK handles ping/pong automatically.

Available rooms

Every room emits its own event type with a typed filter and payload. Click a room for the full schema.
Room IDPurposeRequired filter
polymarket_tradesTrades, redemptions, merges, oracle lifecycle.None
polymarket_oracle_eventsUMA oracle lifecycle (proposals, disputes, resolutions).None
polymarket_asset_pricesRaw Chainlink price ticks for crypto assets.None
polymarket_asset_window_updatesOpen/close candle ticks for crypto assets.None
polymarket_market_metricsPer-market volume, holders, traders, OI.condition_ids
polymarket_event_metricsAggregated event metrics.event_slugs
polymarket_position_metricsPer-outcome volume and trader counts.position_ids
polymarket_tag_metricsAggregated metrics per tag.tags
polymarket_trader_pnlGlobal, market, and event PnL for tracked traders.traders
polymarket_trader_positionsOpen and closed positions per wallet.traders
polymarket_accountspUSD, USDC.e, and MATIC balance updates.wallets
polymarket_order_bookCLOB bid/ask updates.None
polymarket_clob_rewardsCLOB reward configuration changes.None
polymarket_events_streamPeriodic snapshots of event lists at a configurable interval.None
polymarket_markets_streamPeriodic snapshots of market lists at a configurable interval.None
polymarket_position_liquidityPer-position USD order-book liquidity.None
polymarket_market_liquidityPer-market total USD order-book liquidity.None
polymarket_event_liquidityPer-event total USD order-book liquidity.None
Per-message pricing is on WebSocket Pricing.

Reconnection

Subscriptions live only for the lifetime of the connection. If the socket drops, you must reconnect and resubscribe. Use exponential backoff with jitter so transient outages don’t turn into thundering-herd reconnects.
let attempt = 0;
const subscriptions: object[] = [/* track the subscribe payloads you sent */];

function connect() {
  const ws = new WebSocket("wss://api.struct.to/ws?api-key=YOUR_API_KEY");

  ws.onopen = () => {
    attempt = 0;
    for (const sub of subscriptions) ws.send(JSON.stringify(sub));
  };

  ws.onclose = (event) => {
    if (event.code === 1008 || event.code === 4001) return;
    const delay = Math.min(30_000, 1_000 * 2 ** attempt);
    attempt += 1;
    setTimeout(connect, delay + Math.random() * 500);
  };
}

connect();
The TypeScript SDK does this for you, replaying every active subscription on reconnect. See SDK WebSockets. For the full close-code reference, see Errors.

Connection limits

Each plan has a cap on concurrent connections across your organisation. A single connection can subscribe to many rooms, so you rarely need more than a handful of sockets in practice.
PlanConcurrent connections
Free1
Hobby50
Startup250
Scale1,000
EnterpriseUnlimited
See Rate Limits for filter limits and the full per-key throughput matrix.

Next steps

  • Browse the Rooms section for every available stream and its filters.
  • Review WebSocket Pricing for per-message rates.
  • Check out Alerts for webhook-style events over the same protocol.
  • Read Best Practices for cost and resilience patterns.
Last modified on June 16, 2026