> ## Documentation Index
> Fetch the complete documentation index at: https://docs.struct.to/llms.txt
> Use this file to discover all available pages before exploring further.

# Getting Started

> Stream real-time Polymarket data over websockets.

## 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:

| Endpoint                        | Purpose                                                                  |
| ------------------------------- | ------------------------------------------------------------------------ |
| `wss://api.struct.to/ws`        | **Rooms**: subscribe to data streams by topic (trades, prices, metrics). |
| `wss://api.struct.to/ws/alerts` | **Alerts**: webhook-style events pushed over the same connection.        |

This guide covers the Rooms endpoint. See [Alerts](/websockets/alerts/getting-started) 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](/introduction/authentication) for both flows.

## Compression

High-throughput sockets can opt into **zstd** compression, negotiated once at the WebSocket upgrade with the `X-Ws-Compression: zstd` header or the `?compression=zstd` query fallback. When enabled, room envelopes arrive as binary frames of zstd-compressed JSON. See [Compression](/websockets/compression) for decode examples and client support.

## Your first connection

<Steps>
  <Step title="Create an account">
    Sign up at [struct.to/dashboard](https://struct.to/dashboard) and create an organisation.
  </Step>

  <Step title="Generate an API key">
    Open the [API Keys](https://struct.to/dashboard) 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](/introduction/authentication) for key types, JWT public keys, and rotation.
  </Step>

  <Step title="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.

    <CodeGroup>
      ```typescript SDK theme={null}
      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);
      });
      ```

      ```javascript Raw WebSocket theme={null}
      const ws = new WebSocket("wss://api.struct.to/ws?api-key=YOUR_API_KEY");

      ws.onopen = () => {
        ws.send(JSON.stringify({
          type: "join_room",
          payload: { room_id: "polymarket_trades" },
        }));

        ws.send(JSON.stringify({
          type: "room_message",
          payload: {
            room_id: "polymarket_trades",
            message: { action: "subscribe", condition_ids: ["0xabc..."] },
          },
        }));
      };

      ws.onmessage = (event) => {
        const msg = JSON.parse(event.data);
        console.log(msg.type, msg.message);
      };
      ```

      ```python Python theme={null}
      import asyncio
      import json
      import websockets

      async def main():
          uri = "wss://api.struct.to/ws?api-key=YOUR_API_KEY"
          async with websockets.connect(uri) as ws:
              await ws.send(json.dumps({
                  "type": "join_room",
                  "payload": {"room_id": "polymarket_trades"},
              }))

              await ws.send(json.dumps({
                  "type": "room_message",
                  "payload": {
                      "room_id": "polymarket_trades",
                      "message": {
                          "action": "subscribe",
                          "condition_ids": ["0xabc..."],
                      },
                  },
              }))

              async for raw in ws:
                  msg = json.loads(raw)
                  print(msg["type"], msg.get("message"))

      asyncio.run(main())
      ```
    </CodeGroup>
  </Step>

  <Step title="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.
  </Step>
</Steps>

## Connection lifecycle

A typical session follows the same sequence for every room:

```mermaid theme={null}
sequenceDiagram
    participant C as Client
    participant S as Struct
    C->>S: WebSocket handshake (api-key)
    S-->>C: 101 Switching Protocols
    C->>S: join_room { room_id }
    S-->>C: joined_room { room_id }
    C->>S: room_message { action: "subscribe", filters }
    S-->>C: <room>_subscribe_response
    loop Live stream
        S-->>C: room event
    end
    C->>S: ping (every 30s)
    S-->>C: pong
    C->>S: room_message { action: "unsubscribe_all" }
    C->>S: leave_room { room_id }
    C->>S: Close frame
```

## Message protocol

All messages sent and received are JSON. Client messages use a `type` and `payload` envelope:

```json theme={null}
{
  "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.

```json theme={null}
{
  "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`:

```json theme={null}
{
  "type": "room_message",
  "payload": {
    "room_id": "polymarket_trades",
    "message": { "action": "unsubscribe_all" }
  }
}
```

```json theme={null}
{
  "type": "leave_room",
  "payload": { "room_id": "polymarket_trades" }
}
```

### Keepalive

Send a ping every 30 seconds to keep the connection alive:

```json theme={null}
{ "type": "ping" }
```

The server responds with `{ "type": "pong" }`, which you can safely ignore. The TypeScript SDK handles ping/pong automatically.

## Firehose subscriptions

Most rooms accept `subscribe_all: true` on the subscribe message to receive **every** update on that stream instead of a targeted subset:

```json theme={null}
{
  "type": "room_message",
  "payload": {
    "room_id": "polymarket_order_book",
    "message": { "action": "subscribe", "subscribe_all": true }
  }
}
```

Targeting filters (`condition_ids`, `traders`, `wallets`, and so on) are ignored while it's set, while secondary filters like `timeframes` still apply. See [Firehose](/websockets/firehose) for the per-room behaviour table and billing guidance.

## Available rooms

Every room emits its own event type with a typed filter and payload. Click a room for the full schema.

| Room ID                                                                     | Purpose                                                        | Required filter |
| --------------------------------------------------------------------------- | -------------------------------------------------------------- | --------------- |
| [`polymarket_trades`](/websockets/rooms/trades)                             | Trades, redemptions, merges, oracle lifecycle.                 | None            |
| [`polymarket_oracle_events`](/websockets/rooms/oracle-events)               | UMA oracle lifecycle (proposals, disputes, resolutions).       | None            |
| [`polymarket_asset_prices`](/websockets/rooms/asset-prices)                 | Raw Chainlink price ticks for crypto assets.                   | None            |
| [`polymarket_asset_window_updates`](/websockets/rooms/asset-window-updates) | Open/close candle ticks for crypto assets.                     | None            |
| [`polymarket_market_metrics`](/websockets/rooms/market-metrics)             | Per-market volume, holders, traders, OI.                       | `condition_ids` |
| [`polymarket_event_metrics`](/websockets/rooms/event-metrics)               | Aggregated event metrics.                                      | `event_slugs`   |
| [`polymarket_position_metrics`](/websockets/rooms/position-metrics)         | Per-outcome volume and trader counts.                          | `position_ids`  |
| [`polymarket_tag_metrics`](/websockets/rooms/tag-metrics)                   | Aggregated metrics per tag.                                    | `tags`          |
| [`polymarket_trader_pnl`](/websockets/rooms/trader-pnl)                     | Global, market, and event PnL for tracked traders.             | `traders`       |
| [`polymarket_trader_positions`](/websockets/rooms/trader-positions)         | Open and closed positions per wallet.                          | `traders`       |
| [`polymarket_accounts`](/websockets/rooms/accounts)                         | pUSD, USDC.e, and MATIC balance updates.                       | `wallets`       |
| [`polymarket_order_book`](/websockets/rooms/order-book)                     | CLOB bid/ask updates.                                          | None            |
| [`polymarket_clob_rewards`](/websockets/rooms/clob-rewards)                 | CLOB reward configuration changes.                             | None            |
| [`polymarket_events_stream`](/websockets/rooms/events-stream)               | Periodic snapshots of event lists at a configurable interval.  | None            |
| [`polymarket_markets_stream`](/websockets/rooms/markets-stream)             | Periodic snapshots of market lists at a configurable interval. | None            |
| [`polymarket_position_liquidity`](/websockets/rooms/liquidity)              | Per-position USD order-book liquidity.                         | None            |
| [`polymarket_market_liquidity`](/websockets/rooms/liquidity)                | Per-market total USD order-book liquidity.                     | None            |
| [`polymarket_event_liquidity`](/websockets/rooms/liquidity)                 | Per-event total USD order-book liquidity.                      | None            |

Per-message pricing is on [WebSocket Pricing](/websockets/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.

```typescript theme={null}
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](/sdk/websockets#reconnection-and-replay).

For the full close-code reference, see [Errors](/guides/errors#close-codes).

## 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.

| Plan       | Concurrent connections |
| ---------- | ---------------------- |
| Free       | 1                      |
| Hobby      | 50                     |
| Startup    | 250                    |
| Scale      | 1,000                  |
| Enterprise | Unlimited              |

See [Rate Limits](/guides/rate-limits) for filter limits and the full per-key throughput matrix.

## Next steps

* Browse the [Rooms](/websockets/rooms/trades) section for every available stream and its filters.
* Review [WebSocket Pricing](/websockets/pricing) for per-message rates.
* Check out [Alerts](/websockets/alerts/getting-started) for webhook-style events over the same protocol.
* Read [Best Practices](/guides/best-practices) for cost and resilience patterns.
