Skip to main content
StructWebSocket is a typed wrapper around the rooms websocket (wss://api.struct.to/ws). It handles connection, keepalive, reconnect, and replay of subscriptions automatically. Every room has a typed filter and a typed subscribe response.

Connect

import { StructWebSocket } from "@structbuild/sdk";

const ws = new StructWebSocket({ apiKey: "sk_live_xxx" });
await ws.connect();
connect() resolves once the socket reaches the connected state. A socket can safely be kept open for the lifetime of your app; it will auto-reconnect on transient failures.

Subscribe

await ws.subscribe("polymarket_trades", {
  condition_ids: ["0xabc..."],
});

await ws.subscribe("polymarket_order_book", {
  condition_ids: ["0xabc..."],
});

await ws.subscribe("polymarket_market_metrics", {
  condition_ids: ["0xabc..."],
});
subscribe(room, filters?) returns a promise that resolves with the server’s subscribe response (rejected filters, current configuration, and so on). Filters are fully typed per room. Some rooms have optional filters. For those, you can omit the second argument to subscribe without any filter:
await ws.subscribe("polymarket_asset_prices");
await ws.subscribe("polymarket_clob_rewards", { subscribe_all: true });

await ws.subscribe("polymarket_markets_stream", {
  interval_ms: 1000,
  mode: "filter",
  filter: { categories: ["crypto"], min_volume: 50000 },
});

await ws.subscribe("polymarket_events_stream", {
  interval_ms: 500,
  mode: "ids",
  event_slugs: ["us-election-2028"],
});
Calling subscribe a second time on the same room replaces the previous filters.

Listen for events

Register handlers with on(event, listener). Listeners receive fully typed payloads. on returns a disposer function that removes the listener.
const disposeTrades = ws.on("trade_stream_update", (event) => {
  event.condition_id;
  event.price;
  event.size;
  event.side;
});

ws.on("order_book_update", (event) => {
  event.asset_id;
  event.bids;
  event.asks;
});

ws.on("clob_rewards_update", (event) => {
  event.event_type;
  event.condition_id;
  event.reward;
});

disposeTrades();
You can also use once for one-shot listeners, off to remove a specific listener, and removeAllListeners to clear handlers for a given event (or all events).

Unsubscribe and disconnect

ws.unsubscribe("polymarket_trades");
ws.disconnect();
unsubscribe leaves the room and drops its replay entry so it will not be resubscribed on reconnect. disconnect tears down the socket, cancels timers, and clears all state.

Available rooms

RoomFiltersEvent
polymarket_tradescondition_ids?, market_slugs?, event_slugs?, position_ids?, traders?, trade_types?, status?, subscribe_all?trade_stream_update
polymarket_oracle_eventscondition_ids?, market_slugs?, event_slugs?, oracle_event_types?, status?, subscribe_all?oracle_event_update
polymarket_asset_pricesasset_symbols?asset_price_tick, asset_price_window_update
polymarket_asset_window_updatesasset_symbols?, timeframes?asset_window_update
polymarket_market_metricscondition_idsmarket_metrics_update
polymarket_event_metricsevent_slugsevent_metrics_update
polymarket_position_metricsposition_idsposition_metrics_update
polymarket_trader_pnltraderstrader_global_pnl_update, trader_market_pnl_update, trader_event_pnl_update
polymarket_trader_positionstraderstrader_position_update
polymarket_accountswallets, include_usdce?, include_pusd?, include_matic?accounts_update, usdce_update, pusd_update, matic_update
polymarket_order_bookcondition_ids?, position_ids?order_book_update
polymarket_clob_rewardscondition_ids?, subscribe_all?clob_rewards_update
polymarket_events_streaminterval_ms?, mode?, filter?, event_slugs?, event_ids?events_stream_update
polymarket_markets_streaminterval_ms?, mode?, filter?, condition_ids?, market_slugs?, event_slugs?markets_stream_update
See the WebSockets tab for full payload schemas for each room.

Lifecycle events

EventPayloadWhen it fires
connectedvoidSocket reaches the connected state (initial connect and every reconnect).
disconnected{ code, reason }Connection closed, cleanly or otherwise.
reconnecting{ attempt }Auto-reconnect attempt starting.
reconnect_failedErrorAll reconnect attempts exhausted.
auth_failedErrorServer rejected the credentials. No further reconnects are attempted.
errorErrorSocket or listener error.
warningErrorNon-fatal warning from the transport.
ws.on("connected", () => console.log("live"));
ws.on("disconnected", ({ code, reason }) => console.log("gone", code, reason));
ws.on("reconnecting", ({ attempt }) => console.log("retry", attempt));
ws.on("auth_failed", (err) => console.error(err));

Reconnection and replay

When the socket drops, the SDK:
  1. Emits disconnected.
  2. Enters reconnecting state with exponential backoff and jitter.
  3. On each attempt, rebuilds the URL via getJwt (if configured) so the latest JWT is used.
  4. On successful reconnect, replays every active subscription so your handlers keep firing without manual bookkeeping.
Configure behaviour via reconnect:
const ws = new StructWebSocket({
  apiKey: "sk_live_xxx",
  reconnect: {
    maxRetries: 10,
    initialDelayMs: 500,
    maxDelayMs: 30_000,
  },
  subscribeTimeout: 10_000,
});
OptionDefaultDescription
reconnect.maxRetriesInfinityReconnect attempts before emitting reconnect_failed.
reconnect.initialDelayMs1000Base delay between attempts.
reconnect.maxDelayMs30_000Upper bound for the backoff delay.
subscribeTimeout10_000Milliseconds to wait for each subscribe ack before rejecting.

Keepalive

The transport sends a ping every 30 seconds and closes the socket if no pong arrives within 60. This is automatic; you do not need to call it.

Connection state

ws.state;
Possible values: "disconnected", "connecting", "connected", "reconnecting".
Last modified on April 25, 2026