Skip to main content

Command line (wscat)

# Subscribe to the full firehose (all active markets)
wscat -c "wss://ob.polynode.dev/ws?key=pn_live_YOUR_KEY" \
  -x '{"action":"subscribe","markets":["*"]}'

# Subscribe by slug (easiest for specific markets)
wscat -c "wss://ob.polynode.dev/ws?key=pn_live_YOUR_KEY" \
  -x '{"action":"subscribe","markets":["what-price-will-bitcoin-hit-in-march-2026"]}'

# Subscribe by condition ID
wscat -c "wss://ob.polynode.dev/ws?key=pn_live_YOUR_KEY" \
  -x '{"action":"subscribe","markets":["0x561ffbf7de21ef3781c441f30536b026d2b301d7a4a0145a8f526f98db049ba2"]}'

# Subscribe by token ID
wscat -c "wss://ob.polynode.dev/ws?key=pn_live_YOUR_KEY" \
  -x '{"action":"subscribe","markets":["73624432805780182150964443951045800666977811185963019133914618974858599458273"]}'

Finding markets

Use the PolyNode REST API to search for markets:
# Search by keyword
curl -s "https://api.polynode.dev/v1/search?q=bitcoin" | python3 -m json.tool

# Look up by slug (from any Polymarket URL: polymarket.com/event/{slug})
curl -s "https://api.polynode.dev/v1/markets/slug/what-price-will-bitcoin-hit-in-march-2026"

# Look up by condition ID
curl -s "https://api.polynode.dev/v1/markets/condition/0x561ffbf7de21ef3781c441f30536b026d2b301d7a4a0145a8f526f98db049ba2"
The easiest approach: copy the slug from a Polymarket event URL and pass it directly in your subscribe message.

JavaScript (firehose)

const ws = new WebSocket("wss://ob.polynode.dev/ws?key=pn_live_YOUR_KEY");

ws.onopen = () => {
  ws.send(JSON.stringify({ action: "subscribe", markets: ["*"] }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  if (msg.type === "subscribed") {
    console.log(`Firehose active: ${msg.markets} tokens`);
  } else if (msg.type === "batch") {
    console.log(`${msg.count} updates at ${msg.ts}`);
    for (const update of msg.updates) {
      if (update.type === "price_change") {
        for (const asset of update.assets) {
          console.log(`  ${update.slug} [${asset.outcome}]: ${asset.price}`);
        }
      }
    }
  }
};

JavaScript (specific markets)

const ws = new WebSocket("wss://ob.polynode.dev/ws?key=pn_live_YOUR_KEY");

ws.onopen = () => {
  // Subscribe by slug — both sides (Yes + No) are included automatically
  ws.send(JSON.stringify({
    action: "subscribe",
    markets: [
      "what-price-will-bitcoin-hit-in-march-2026",
      "netanyahu-out-before-2027"
    ]
  }));
};

// Maintain local orderbook state
const books = {};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  switch (msg.type) {
    case "subscribed":
      console.log(`Subscribed to ${msg.markets} tokens`);
      console.log(`Resolved from: ${JSON.stringify(msg.resolved_from)}`);
      break;

    case "snapshot_batch":
      for (const snap of msg.snapshots) {
        books[snap.asset_id] = { bids: snap.bids, asks: snap.asks };
        console.log(`Snapshot: ${snap.event_title} [${snap.outcome}] - ${snap.bids.length}b/${snap.asks.length}a`);
      }
      break;

    case "batch":
      for (const update of msg.updates) {
        if (update.type === "price_change") {
          for (const asset of update.assets) {
            console.log(`${update.slug} [${asset.outcome}]: ${asset.price}`);
          }
        } else if (update.type === "book_update") {
          const book = books[update.asset_id];
          if (book) {
            applyDelta(book.bids, update.bids);
            applyDelta(book.asks, update.asks);
          }
        }
      }
      break;
  }
};

// Apply incremental orderbook updates
function applyDelta(existing, deltas) {
  for (const delta of deltas) {
    const idx = existing.findIndex(l => l.price === delta.price);
    if (delta.size === "0") {
      if (idx >= 0) existing.splice(idx, 1);
    } else if (idx >= 0) {
      existing[idx].size = delta.size;
    } else {
      existing.push(delta);
    }
  }
}

ws.onclose = () => setTimeout(() => location.reload(), 3000);

Python (firehose)

import asyncio
import json
import websockets

async def stream_firehose():
    url = "wss://ob.polynode.dev/ws?key=pn_live_YOUR_KEY"

    async with websockets.connect(url, max_size=10_000_000) as ws:
        await ws.send(json.dumps({
            "action": "subscribe",
            "markets": ["*"]
        }))

        async for message in ws:
            data = json.loads(message)

            if data["type"] == "subscribed":
                print(f"Firehose active: {data['markets']} tokens")

            elif data["type"] == "batch":
                for update in data["updates"]:
                    if update["type"] == "price_change":
                        for asset in update["assets"]:
                            print(f"  {update['slug']} [{asset['outcome']}]: {asset['price']}")

asyncio.run(stream_firehose())
Set max_size to at least 10 MB when connecting to the firehose. Batch messages can be large when many markets update in the same 100ms window.

Python (specific markets)

import asyncio
import json
import websockets

async def stream_orderbook():
    url = "wss://ob.polynode.dev/ws?key=pn_live_YOUR_KEY"

    async with websockets.connect(url) as ws:
        # Subscribe by slug — resolves to all token IDs for those events
        await ws.send(json.dumps({
            "action": "subscribe",
            "markets": [
                "what-price-will-bitcoin-hit-in-march-2026",
                "netanyahu-out-before-2027",
            ]
        }))

        async for message in ws:
            data = json.loads(message)

            if data["type"] == "subscribed":
                print(f"Subscribed to {data['markets']} tokens")
                print(f"Resolved from: {data['resolved_from']}")

            elif data["type"] == "snapshot_batch":
                for snap in data["snapshots"]:
                    print(f"Snapshot: {snap['event_title']} [{snap['outcome']}]")
                    print(f"  Bids: {len(snap['bids'])} levels")
                    print(f"  Asks: {len(snap['asks'])} levels")

            elif data["type"] == "batch":
                for update in data["updates"]:
                    if update["type"] == "price_change":
                        for asset in update["assets"]:
                            print(f"  {update['slug']} [{asset['outcome']}]: {asset['price']}")

                    elif update["type"] == "book_update":
                        bids = len(update.get("bids", []))
                        asks = len(update.get("asks", []))
                        print(f"  Book delta: {update['slug']} [{update['outcome']}] +{bids}b/+{asks}a")

asyncio.run(stream_orderbook())

Python with compression

import asyncio
import json
import zlib
import websockets

async def stream_compressed():
    url = "wss://ob.polynode.dev/ws?key=pn_live_YOUR_KEY&compress=zlib"

    async with websockets.connect(url) as ws:
        await ws.send(json.dumps({
            "action": "subscribe",
            "markets": ["what-price-will-bitcoin-hit-in-march-2026"]
        }))

        async for message in ws:
            if isinstance(message, bytes):
                text = zlib.decompress(message, -zlib.MAX_WBITS).decode("utf-8")
                data = json.loads(text)
            else:
                data = json.loads(message)

            print(f"[{data.get('type')}] {json.dumps(data)[:200]}")

asyncio.run(stream_compressed())

Mixed identifier subscribe

You can mix slugs, condition IDs, and token IDs in a single subscribe:
await ws.send(json.dumps({
    "action": "subscribe",
    "markets": [
        "what-price-will-bitcoin-hit-in-march-2026",                              # slug
        "0xd1796c09d0d6f876f8580086ae9808ec991784e3a74b25a1830a25de71a78c96",      # condition ID
        "73624432805780182150964443951045800666977811185963019133914618974858599458273"  # token ID
    ]
}))