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

# WebSocket

> Stream live prediction-market events over a single persistent connection — no polling, no rate-limit juggling. The first live stream delivers Polymarket trades as they execute, with server-side filtering by wallet, market, outcome, and more so you only receive the events you care about.

## Connection

Open a WebSocket connection to the gateway and authenticate with your Falcon API token as a Bearer credential. Your token is verified at the connection upgrade, so only authenticated clients stream.

```text WebSocket URL theme={null}
wss://ripple.heisenberg.so/ws
```

```text HTTP Header (sent on the upgrade request) theme={null}
Authorization: Bearer YOUR_API_TOKEN
```

> [!TIP]
>
> **Connect from your backend**
>
> Stream from a server-side client (Node, Python, Go) and send your token in the `Authorization` header — the standard setup for production streaming. Keep your API token server-side. See [Authentication](/docs/getting-started/authentication) for how to obtain a token.

## Event types

Every stream is identified by an event type. Today `polymarket.trade` is live, the first of more to come. Access to each type is governed by your plan. Call `GET /event-types` for the catalog you're authorized for — it returns each type's field schema, including a per-field `filterable` flag that tells you exactly which fields you can filter on.

```bash cURL theme={null}
curl https://ripple.heisenberg.so/event-types \
  -H "Authorization: Bearer YOUR_API_TOKEN"
```

```json 200 Response (abridged) theme={null}
[
  {
    "name": "polymarket.trade",
    "label": "polymarket_trades_realtime",
    "description": "",
    "schema": {
      "fields": [
        { "name": "slug",         "type": "string", "filterable": true },
        { "name": "proxy_wallet", "type": "string", "filterable": true },
        { "name": "outcome",      "type": "string", "filterable": true },
        { "name": "price",        "type": "float",  "filterable": false }
      ]
    }
  }
]
```

Abridged — `schema.fields` lists all 24 fields of `polymarket.trade`. See [Event messages](#event-messages) for the full payload.

## Subscribe

Once connected, send a `subscribe` frame. Each frame can open multiple subscriptions at once — pass one entry per stream in the `subscriptions` array. Omit `filters` (or pass `{}`) to receive *every* event of that type.

### Subscribe to all trades

```json JSON client to server theme={null}
{
  "action": "subscribe",
  "subscriptions": [
    { "type": "polymarket.trade", "filters": {} }
  ]
}
```

### Subscribe to one market

```json JSON client to server theme={null}
{
  "action": "subscribe",
  "subscriptions": [
    {
      "type": "polymarket.trade",
      "filters": { "slug": "sol-updown-15m-1779670800" }
    }
  ]
}
```

The server replies with a `subscribed` acknowledgement listing the event types it granted and a unique `id` for each subscription. Keep each `id` — you need it to unsubscribe. The `filters` you sent are echoed back when present.

```json JSON server to client theme={null}
{
  "action": "subscribed",
  "types": ["polymarket.trade"],
  "subscriptions": [
    {
      "id": "cede7755-c0a6-41b8-b316-ca4377dedb5d",
      "type": "polymarket.trade",
      "filters": { "slug": "sol-updown-15m-1779670800" }
    }
  ]
}
```

> [!TIP]
>
> **Control messages vs. events**
>
> Acknowledgements, errors, and notices carry an `action` field (`subscribed`, `error`, `rate_limit`, …). Data events instead carry a `type` and a `data` object. Branch on whichever field is present. Mixed frames are handled gracefully — the acknowledgement grants every type your plan includes, so the rest of the subscription always goes through.

## Filters

Filters are matched server-side. Only the filterable fields below are accepted — pass any combination as key/value pairs inside `filters`. To track multiple wallets or markets at once, open one subscription per target (each with its own `id`).

Filterable fields: `proxy_wallet`, `slug`, `condition_id`, `event_slug`, `outcome`, `outcome_index`, `side`, `asset`, `maker`, `taker`, `transaction_hash`, `order_hash`.

`GET /event-types` is the authoritative source for which fields are filterable on each event type. To follow several wallets or markets, open one subscription per target — each gets its own `id` for independent unsubscribe.

## Build a Subscription

Pick your filters and copy the generated `subscribe` frame — or a ready-to-run Node or Python client — straight into your app.

## Event messages

Each event is a JSON frame carrying the event `type`, its `source`, the delivery `timestamp`, and a `data` object with the trade. Here is a live `polymarket.trade` event (long hashes abbreviated):

```json JSON server to client theme={null}
{
  "type": "polymarket.trade",
  "source": "polymarket-trade-scraper",
  "timestamp": "2026-05-27T13:50:00.167247+00:00",
  "data": {
    "id": "0x8902f2...498832_0x31ba8c...a5aa09",
    "side": "BUY",
    "size": 11,
    "price": 0.53,
    "timestamp": "2026-05-27T13:49:52+00:00",
    "transaction_hash": "0x8902f299...d6d498832",
    "order_hash": "0x31ba8c8c...454aa5aa09",
    "block_number": 87514326,
    "condition_id": "0x9ad19824...2d7f2d9",
    "asset": "67025362...363793",
    "title": "Bitcoin Up or Down - May 27, 9:50AM-9:55AM ET",
    "slug": "btc-updown-5m-1779889800",
    "event_slug": "btc-updown-5m",
    "outcome": "Up",
    "outcome_index": 0,
    "icon": "https://polymarket-upload.s3.../BTC+fullsize.png",
    "maker": "0x55338c...d1b8220",
    "taker": "0x6833c0...1b5cfe1",
    "proxy_wallet": "0x55338c...d1b8220",
    "name": null,
    "pseudonym": null,
    "bio": null,
    "profile_image": null,
    "profile_image_optimized": null
  }
}
```

## Filterable fields

The fields you can filter on (from `/event-types`). Every event also carries the additional non-filterable fields shown in the payload above.

| Field | Type | Description |
| - | - | - |
| `proxy_wallet` | `string` | Trader's proxy wallet |
| `slug` | `string` | Market slug |
| `condition_id` | `string` | Market condition ID |
| `event_slug` | `string` | Parent event slug |
| `outcome` | `string` | Outcome traded (e.g. Up, No) |
| `outcome_index` | `integer` | Index of the traded outcome |
| `side` | `string` | Trade direction — BUY or SELL |
| `asset` | `string` | Outcome token (asset) ID |
| `maker` | `string` | Maker wallet address |
| `taker` | `string` | Taker wallet address |
| `transaction_hash` | `string` | On-chain transaction hash |
| `order_hash` | `string` | On-chain order hash |

## Unsubscribe

Stop one or more streams by sending an `unsubscribe` frame with the `subscription_ids` returned when you subscribed. The server confirms with an `unsubscribed` acknowledgement.

```json JSON client to server theme={null}
{
  "action": "unsubscribe",
  "subscription_ids": ["cede7755-c0a6-41b8-b316-ca4377dedb5d"]
}
```

```json JSON server to client theme={null}
{
  "action": "unsubscribed",
  "subscription_ids": ["cede7755-c0a6-41b8-b316-ca4377dedb5d"]
}
```

## Connection lifecycle

### Heartbeat

Connections are kept healthy by a built-in ping/pong heartbeat. Most WebSocket libraries respond to pings automatically, so there's nothing to implement — your stream stays alive as long as your client is connected.

### Reconnection

Connections are long-lived. For a resilient client, reconnect automatically with exponential backoff and re-send your `subscribe` frames once reconnected — the standard pattern for any streaming integration. A close with code `4001` is simply a cue to reconnect to a fresh upstream feed, and deploys roll out with graceful connection draining so reconnects stay clean. You can confirm gateway health any time with `GET /health` (returns `200 OK`).

## Rate limits and quotas

Streaming allowances depend on your plan. The gateway enforces limits in-band so your client always knows what happened.

* **Active subscriptions** — Your plan caps how many concurrent subscriptions you can hold on one connection.
* **Message rate** — If you exceed your delivery quota, the server sends a `rate_limit` frame and may pause delivery until the window resets.
* **Subscribe in one frame** — Batch multiple streams in a single `subscribe` message; each entry counts toward your subscription limit.
* **One connection is all you need** — Multiplex every stream on a single WebSocket instead of opening many connections.

```json JSON server to client theme={null}
{
  "action": "rate_limit",
  "message": "message rate limit reached"
}
```

> [!TIP]
>
> **Scale up any time**
>
> Need higher subscription or message limits? Upgrade your plan from the [Falcon dashboard](https://api.polymarketanalytics.com).

## Errors and notices

The gateway always answers — any protocol issue comes back as a clear, in-band `error` message that names the relevant `types` and a human-readable `message`, so your client knows exactly what to do. For example, requesting a type your plan doesn't include yet:

```json JSON server to client theme={null}
{
  "action": "error",
  "types": ["polymarket.does_not_exist"],
  "message": "unauthorized: polymarket.does_not_exist"
}
```

Filtering on a non-filterable field:

```json JSON server to client theme={null}
{
  "action": "error",
  "types": ["polymarket.trade"],
  "message": "non-filterable keys for polymarket.trade: not_a_real_field"
}
```

Authentication is checked at the connection upgrade with a standard `401`, so only valid tokens stream.

## Node.js (ws)

```javascript Node.js theme={null}
const WebSocket = require('ws');

const SUBSCRIBE = {
  action: 'subscribe',
  subscriptions: [
    {
      type: 'polymarket.trade',
      filters: { slug: 'btc-updown-5m-1779889800' },
    },
  ],
};

function connect() {
  const ws = new WebSocket('wss://ripple.heisenberg.so/ws', {
    headers: { Authorization: 'Bearer ' + process.env.FALCON_API_TOKEN },
  });

  ws.on('open', () => {
    ws.send(JSON.stringify(SUBSCRIBE));
  });

  ws.on('message', (raw) => {
    const msg = JSON.parse(raw);

    if (msg.action === 'subscribed') {
      console.log('subscribed:', msg.subscriptions.map((s) => s.id));
      return;
    }
    if (msg.action === 'error' || msg.action === 'rate_limit') {
      console.warn(msg.action, msg.message);
      return;
    }
    if (msg.type === 'polymarket.trade') {
      const t = msg.data;
      console.log(t.side, t.size, '@', t.price, '-', t.slug);
    }
  });

  ws.on('close', (code) => {
    if (code === 4001) setTimeout(connect, 1000);
  });
}

connect();
```

## Python (websockets)

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

async def main():
    url = "wss://ripple.heisenberg.so/ws"
    headers = {"Authorization": f"Bearer {os.environ['FALCON_API_TOKEN']}"}
    async with websockets.connect(url, additional_headers=headers) as ws:
        await ws.send(json.dumps({
            "action": "subscribe",
            "subscriptions": [
                {"type": "polymarket.trade", "filters": {"slug": "btc-updown-5m-1779889800"}},
            ],
        }))
        async for raw in ws:
            msg = json.loads(raw)
            if "action" in msg:              # subscribed / error / rate_limit
                print(msg)
            elif msg.get("type") == "polymarket.trade":
                t = msg["data"]
                print(t["side"], t["size"], "@", t["price"], "-", t["slug"])

asyncio.run(main())
```

## Need more streams?

More event types are on the way. To request early access or a dedicated event type, reach us at [contact@heisenberg.so](mailto:contact@heisenberg.so).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.