Livev1.1.2

WebSockets

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 Heisenberg API token as a Bearer credential. Your token is verified at the connection upgrade, so only authenticated clients stream.

WebSocket URL
wss://ripple.heisenberg.so/ws
HTTP Header (sent on the upgrade request)
Authorization: Bearer YOUR_API_TOKEN

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.

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.

cURL
curl https://ripple.heisenberg.so/event-types \
  -H "Authorization: Bearer YOUR_API_TOKEN"
200 · Response (abridged)
[
  {
    "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 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 · client → server
{
  "action": "subscribe",
  "subscriptions": [
    { "type": "polymarket.trade", "filters": {} }
  ]
}

Subscribe to one market

JSON · client → server
{
  "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 · server → client
{
  "action": "subscribed",
  "types": ["polymarket.trade"],
  "subscriptions": [
    {
      "id": "cede7755-c0a6-41b8-b316-ca4377dedb5d",
      "type": "polymarket.trade",
      "filters": { "slug": "sol-updown-15m-1779670800" }
    }
  ]
}

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 with exact equality against the event's top-level data fields, before delivery — so you receive only the events you want. For polymarket.trade, the filterable fields are:

proxy_walletslugcondition_idevent_slugoutcomeoutcome_indexsideassetmakertakertransaction_hashorder_hash

These are the fields you can filter on — GET /event-types is the authoritative source. To track several wallets or markets independently, open one subscription per target in the same subscribe frame.

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.

FiltersNo filters — all trades
{
  "action": "subscribe",
  "subscriptions": [
    {
      "type": "polymarket.trade",
      "filters": {}
    }
  ]
}

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 · server → client
{
  "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.

FieldTypeDescription
proxy_walletstringTrader's proxy wallet
slugstringMarket slug
condition_idstringMarket condition ID
event_slugstringParent event slug
outcomestringOutcome traded (e.g. Up, No)
outcome_indexintegerIndex of the traded outcome
sidestringTrade direction — BUY or SELL
assetstringOutcome token (asset) ID
makerstringMaker wallet address
takerstringTaker wallet address
transaction_hashstringOn-chain transaction hash
order_hashstringOn-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 · client → server
{
  "action": "unsubscribe",
  "subscription_ids": ["cede7755-c0a6-41b8-b316-ca4377dedb5d"]
}
JSON · server → client
{
  "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 & Quotas

Each plan includes streaming allowances sized for production workloads. A few simple practices keep your integration efficient and well within them:

  • Active subscriptions — a single connection holds many subscriptions at once; your plan sets how many run concurrently.
  • Message rate — events are delivered at your plan's throughput. If you reach it, the gateway sends a single rate_limit notice for that window and keeps your connection open. Filters keep your feed focused and comfortably within budget.
  • Subscribe in one frame — open all your subscriptions in a single subscribe frame. It's the fastest way to start streaming and keeps your client tidy.
  • One connection is all you need — a single connection carries every subscription, so there's rarely a reason to open more. It's the simplest setup and scales effortlessly.
JSON · server → client
{
  "action": "rate_limit",
  "message": "message rate limit reached"
}

Scale up any time

Your plan includes a streaming allowance that renews each billing period. Need more throughput or more concurrent subscriptions? Upgrade any time — see Pricing & Plans for what each tier includes.

Errors & 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 · server → client
{
  "action": "error",
  "types": ["polymarket.does_not_exist"],
  "message": "unauthorized: polymarket.does_not_exist"
}

Filtering on a non-filterable field:

JSON · server → client
{
  "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.

Full Example

A minimal client that connects, subscribes to one market, prints each trade, and reconnects on 4001.

Node.js (ws)

JavaScript
import WebSocket from 'ws';

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

  ws.on('open', () => {
    ws.send(JSON.stringify({
      action: 'subscribe',
      subscriptions: [
        { type: 'polymarket.trade', filters: { slug: 'btc-updown-5m-1779889800' } },
      ],
    }));
  });

  ws.on('message', (raw) => {
    const msg = JSON.parse(raw.toString());
    if (msg.action === 'subscribed') {
      console.log('subscribed:', msg.subscriptions.map((s) => s.id));
    } else if (msg.action === 'error' || msg.action === 'rate_limit') {
      console.warn(msg.action + ':', msg.message);
    } else if (msg.type === 'polymarket.trade') {
      const t = msg.data;
      console.log(t.side, t.size, '@', t.price, '·', t.slug);
    }
  });

  // The ws library answers server pings automatically.
  ws.on('close', (code) => {
    if (code === 4001) setTimeout(connect, 1000); // upstream interrupted — reconnect + re-subscribe
  });
}

connect();

Python (websockets)

Python
import asyncio, json, os
import websockets

async def main():
    url = "wss://ripple.heisenberg.so/ws"
    headers = {"Authorization": f"Bearer {os.environ['HEISENBERG_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.