Skip to main content
PolyNode’s WebSocket is the fastest way to get Polymarket trade data. Settlements are delivered to your connection 1–2 blocks before they confirm on-chain (median 2.9 seconds, up to 5 seconds early).

Up to 5 second edge

Settlements detected and enriched before on-chain confirmation — typically 2–5 seconds (1–2 blocks) early.

Filtered subscriptions

Subscribe by wallet, token, market slug, side, size, or event type. Only receive what you need.

Enriched events

Every event includes market title, outcome name, slug, and image. No secondary lookups needed.

Chainlink price feeds

Real-time BTC/USD prices from Chainlink Data Streams (~1/second). Subscribe with "type": "chainlink" on the same connection.

Oracle resolution stream

UMA Optimistic Oracle events: market resolutions, disputes, proposals, and admin actions. Subscribe with "type": "oracle" to track the full resolution lifecycle.
Save ~60% bandwidth with compression. Add &compress=zlib to your connection URL and decompress binary frames with standard inflateRaw. Zero latency impact, recommended for production. See compression docs →

Quick start

1

Get an API key

curl -s -X POST https://api.polynode.dev/v1/keys \
  -H "Content-Type: application/json" \
  -d '{"name": "my-app"}'
Save the pn_live_... key from the response.
2

Connect and subscribe

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

ws.onopen = () => {
  ws.send(JSON.stringify({ action: "subscribe", type: "settlements" }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type === "settlement") {
    const d = msg.data;
    console.log(`${d.taker_side} ${d.taker_size} @ ${d.taker_price} | ${d.market_title}`);
  }
};
3

Receive events

You’ll get a snapshot of recent matching events, then live settlement events as they happen:
{
  "data": {
    "block_number": null,
    "condition_id": "0xb27aae79542981f7a476fb93b7ec456f...",
    "detected_at": 1772500000000,
    "event_title": "Bitcoin Price Milestones",
    "event_type": "settlement",
    "market_image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/...",
    "market_slug": "bitcoin-100k-2026",
    "market_title": "Will Bitcoin hit $100K in 2026?",
    "neg_risk": true,
    "outcome": "Yes",
    "status": "pending",
    "taker_base_fee": 0,
    "taker_price": 0.72,
    "taker_side": "BUY",
    "taker_size": 500.0,
    "taker_token": "21742633143463906290569404...",
    "taker_wallet": "0xabcdef1234567890...",
    "tick_size": 0.01,
    "tokens": {
      "21742633143463906290569404...": "Yes",
      "48393153309905564935848131...": "No"
    },
    "trades": [
      {
        "maker": "0x999888777666...",
        "maker_amount": "360000000",
        "price": 0.72,
        "side": "BUY",
        "signer": "0xabc123...",
        "size": 500.0,
        "taker": "0xabcdef1234567890...",
        "taker_amount": "500000000",
        "token_id": "21742633..."
      }
    ],
    "tx_hash": "0x1a2b3c4d5e6f..."
  },
  "timestamp": 1772500000000,
  "type": "settlement"
}

Connection URL

wss://ws.polynode.dev/ws?key=pn_live_YOUR_KEY
With compression (~60% bandwidth savings):
wss://ws.polynode.dev/ws?key=pn_live_YOUR_KEY&compress=zlib
The API key is passed as a query parameter. Keys starting with pn_live_ or qm_live_ are accepted.

Multiple streams, one connection

PolyNode supports multiple subscription types on the same WebSocket:
StreamPurposeSubscribe
SettlementsDecoded Polymarket events with filtering{"action": "subscribe", "type": "settlements"}
OracleUMA resolution lifecycle (resolutions, disputes, flags){"action": "subscribe", "type": "oracle"}
ChainlinkReal-time BTC/USD prices from Data Streams{"action": "subscribe", "type": "chainlink"}
All can run simultaneously on the same connection.

Heartbeat

The server sends a heartbeat every 30 seconds:
  • A WebSocket-level Ping frame (handled automatically by WS clients)
  • A text message: {"type": "heartbeat", "ts": 1772386305181}
If no heartbeat arrives within ~35 seconds, the connection is dead — reconnect. The server monitors client health via Pong responses to its Ping frames. If no Pong is received within 90 seconds (3 missed heartbeats), the server closes the connection. Standard WebSocket clients handle Pong automatically — no code required on your end. You can also send a client-side ping:
{"action": "ping"}
Response: {"type": "pong"}

Connection lifecycle

Connect → wss://ws.polynode.dev/ws?key=pn_live_...

Send → {"action": "subscribe", "type": "settlements", ...}

Receive ← {"type": "snapshot", "count": 20, "events": [...]}
Receive ← {"type": "subscribed", "subscriber_id": "...", "subscription_id": "...:1"}

Send → {"action": "subscribe", ...}        (additional subscriptions stack)
Receive ← {"type": "subscribed", "subscription_id": "...:2"}

Receive ← {"type": "settlement", ...}     (live events)
Receive ← {"type": "status_update", ...}
Receive ← Ping frame                       (every 30s)
Send   → Pong frame                        (automatic)
Receive ← {"type": "heartbeat", ...}       (every 30s)

Send → {"action": "unsubscribe", "subscription_id": "...:1"}  (remove one)
Send → {"action": "unsubscribe"}                               (remove all)
Receive ← {"type": "unsubscribed"}

Error handling

Invalid messages return structured errors:
{
  "type": "error",
  "code": "invalid_json",
  "message": "Message is not valid JSON."
}
Error codeCause
invalid_jsonMessage is not valid JSON
invalid_messageUnknown action or missing required fields
unresolved_slugsSlug not found in market metadata
unresolved_condition_idsCondition ID not found in metadata

Reconnection

PolyNode does not send a close frame before disconnecting. Implement reconnection with exponential backoff:
let delay = 1000;

function connect() {
  const ws = new WebSocket("wss://ws.polynode.dev/ws?key=pn_live_...");
  ws.onopen = () => {
    delay = 1000; // Reset on success
    ws.send(JSON.stringify({ action: "subscribe", type: "settlements" }));
  };
  ws.onclose = () => setTimeout(connect, Math.min(delay *= 2, 30000));
}