PolymarketPolymarketDeveloper15 min read2025-12-11

Polymarket WebSocket Tutorial: Real-Time Data Streaming Guide

AL - Founder of PolyTrack, Polymarket trader & analyst

AL

Founder of PolyTrack, Polymarket trader & analyst

Polymarket WebSocket Tutorial: Real-Time Data Streaming Guide - Developer Guide for Polymarket Traders | PolyTrack Blog

Real-time data is the foundation of successful trading bots and analytics tools on Polymarket. While REST APIs are great for fetching data on demand, WebSocket connections push updates instantly—giving you a critical edge when milliseconds matter. This comprehensive tutorial covers everything you need to stream real-time Polymarket data using both the CLOB WebSocket and RTDS (Real-Time Data Socket).

Understanding Polymarket's WebSocket Architecture

Polymarket provides two distinct WebSocket services, each optimized for different use cases:

CLOB WebSocket vs RTDS

FeatureCLOB WebSocketRTDS
Endpointwss://ws-subscriptions-clob.polymarket.comwss://ws-live-data.polymarket.com
Best ForOrder book streaming, trading, user ordersBroad market data, prices, activity feeds
Channelsmarket, userTopic-based (activity, prices, clob_market, etc.)
Max Instruments500 per connectionNot specified
Unsubscribe SupportNot supportedFully supported

Use CLOB WebSocket when building trading bots that need order book data and trade execution notifications. Use RTDS for dashboards, analytics tools, and monitoring broader market activity.

CLOB WebSocket: Real-Time Order Book Data

The CLOB WebSocket provides level 2 order book data and is essential for building competitive trading bots.

Connection URLs

  • Market Channel: wss://ws-subscriptions-clob.polymarket.com/ws/market
  • User Channel: wss://ws-subscriptions-clob.polymarket.com/ws/user

Python: Connect to Market Channel

Here's a complete Python example for streaming order book updates:

from websocket import WebSocketApp
import json
import threading
import time

class PolymarketMarketWS:
    def __init__(self, asset_ids):
        self.asset_ids = asset_ids
        self.url = "wss://ws-subscriptions-clob.polymarket.com"
        self.ws = None

    def on_open(self, ws):
        print("Connected to Polymarket CLOB WebSocket")
        # Subscribe to market channel
        subscription = {
            "assets_ids": self.asset_ids,
            "type": "market"
        }
        ws.send(json.dumps(subscription))
        self.start_ping_thread()

    def on_message(self, ws, message):
        data = json.loads(message)
        event_type = data.get("event_type")

        if event_type == "book":
            print(f"Order Book Update:")
            print(f"  Best Bid: {data.get('bids', [[]])[0]}")
            print(f"  Best Ask: {data.get('asks', [[]])[0]}")
        elif event_type == "price_change":
            for change in data.get("price_changes", []):
                print(f"Price: Bid {change.get('best_bid')} / Ask {change.get('best_ask')}")
        elif event_type == "last_trade_price":
            print(f"Trade: {data.get('price')} @ {data.get('size')} ({data.get('side')})")

    def on_error(self, ws, error):
        print(f"Error: {error}")

    def on_close(self, ws, close_status_code, close_msg):
        print("WebSocket connection closed")

    def start_ping_thread(self):
        def send_ping():
            while self.ws and self.ws.sock and self.ws.sock.connected:
                time.sleep(10)
                try:
                    self.ws.send("PING")
                except:
                    break

        ping_thread = threading.Thread(target=send_ping)
        ping_thread.daemon = True
        ping_thread.start()

    def connect(self):
        self.ws = WebSocketApp(
            self.url + "/ws/market",
            on_open=self.on_open,
            on_message=self.on_message,
            on_error=self.on_error,
            on_close=self.on_close
        )
        self.ws.run_forever()

# Usage - replace with actual token ID
asset_ids = ["21742633143463906290569050155826241533067272736897614950488156847949938836455"]
market_ws = PolymarketMarketWS(asset_ids)
market_ws.connect()

Installation

Install required packages: pip install websocket-client py-clob-client

Message Types Explained

1. Book Message (Full Order Book Snapshot)

{
  "event_type": "book",
  "asset_id": "21742633...",
  "market": "0x...",
  "timestamp": 1234567890000,
  "bids": [
    {"price": "0.52", "size": "100"},
    {"price": "0.51", "size": "250"}
  ],
  "asks": [
    {"price": "0.53", "size": "150"},
    {"price": "0.54", "size": "200"}
  ]
}

2. Price Change Message (Incremental Updates)

{
  "event_type": "price_change",
  "market": "0x...",
  "timestamp": 1234567890000,
  "price_changes": [
    {
      "asset_id": "...",
      "best_bid": "0.52",
      "best_ask": "0.53",
      "changes": [
        {"price": "0.52", "side": "BUY", "size": "100"}
      ]
    }
  ]
}

3. Last Trade Price Message

{
  "event_type": "last_trade_price",
  "asset_id": "...",
  "market": "0x...",
  "price": "0.52",
  "size": "100",
  "side": "BUY",
  "timestamp": 1234567890000
}

See What Whales Are Trading Right Now

Get instant alerts when top traders make moves. Track P&L, win rates, and copy winning strategies.

Track Whales Free

Free forever. No credit card required.

Authenticated User Channel

The user channel provides real-time updates on your orders and trades. This requires API authentication.

Getting API Credentials

from py_clob_client.client import ClobClient
import os

host = "https://clob.polymarket.com"
private_key = os.getenv("PRIVATE_KEY")  # Your wallet private key

client = ClobClient(host, key=private_key, chain_id=137)
api_creds = client.create_or_derive_api_creds()

print(f"API Key: {api_creds.api_key}")
print(f"Secret: {api_creds.api_secret}")
print(f"Passphrase: {api_creds.api_passphrase}")

Subscribing to User Channel

from websocket import WebSocketApp
import json

# Use credentials from above
auth = {
    "apiKey": api_creds.api_key,
    "secret": api_creds.api_secret,
    "passphrase": api_creds.api_passphrase
}

def on_open(ws):
    subscription = {
        "markets": ["0xbd31dc8a20211944f6b70f31557f1001557b59905b7738480ca09bd4532f84af"],
        "type": "user",
        "auth": auth
    }
    ws.send(json.dumps(subscription))

def on_message(ws, message):
    data = json.loads(message)
    event_type = data.get("event_type")

    if event_type == "order":
        print(f"Order Update: {data.get('order_id')} - Status: {data.get('status')}")
    elif event_type == "trade":
        print(f"Trade Executed: {data.get('price')} x {data.get('size')}")

ws = WebSocketApp(
    "wss://ws-subscriptions-clob.polymarket.com/ws/user",
    on_open=on_open,
    on_message=on_message
)
ws.run_forever()

RTDS: Real-Time Data Socket

RTDS is better suited for building dashboards and analytics tools that need broader market data beyond just order books.

Available RTDS Topics

TopicTypesAuth Required
activitytrades, orders_matchedNo
crypto_pricesupdate (BTC, ETH, SOL, DOGE, XRP)No
equity_pricesupdate (AAPL, TSLA, MSFT, etc.)No
clob_marketagg_orderbook, price_change, last_trade_priceNo
clob_userorder, tradeYes
commentscomment_created, reaction_createdNo

TypeScript: Using the Official RTDS Client

import { RealTimeDataClient, Message } from "@polymarket/real-time-data-client";

// npm install @polymarket/real-time-data-client

const onMessage = (message: Message): void => {
  console.log(`[${message.topic}:${message.type}]`, message.payload);
};

const onConnect = (client: RealTimeDataClient): void => {
  console.log("Connected to RTDS");

  // Subscribe to market trades
  client.subscribe({
    subscriptions: [
      {
        topic: "activity",
        type: "trades",
        filters: `{"market_slug":"will-bitcoin-hit-100k"}`,
      },
    ],
  });

  // Subscribe to crypto prices
  client.subscribe({
    subscriptions: [
      {
        topic: "crypto_prices",
        type: "update",
      },
    ],
  });
};

const rtdsClient = new RealTimeDataClient({
  onMessage,
  onConnect,
});

rtdsClient.connect();

JavaScript: Simple Node.js Implementation

const WebSocket = require('ws');

const ws = new WebSocket('wss://ws-live-data.polymarket.com');

ws.on('open', () => {
  console.log('Connected to RTDS');

  // Subscribe to activity feed
  ws.send(JSON.stringify({
    action: "subscribe",
    subscriptions: [
      {
        topic: "activity",
        type: "trades"
      },
      {
        topic: "crypto_prices",
        type: "update"
      }
    ]
  }));

  // Send ping every 5 seconds
  setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.ping();
    }
  }, 5000);
});

ws.on('message', (data) => {
  const message = JSON.parse(data);
  console.log(`[${message.topic}] ${message.type}:`, message.payload);
});

ws.on('error', console.error);

Critical: Heartbeat and Reconnection

WebSocket connections will drop without proper heartbeat handling. This is essential for production systems.

Heartbeat Requirements

  • CLOB WebSocket: Send "PING" every 10 seconds
  • RTDS: Send PING frame every 5 seconds

Production-Ready Reconnection Pattern

import time
import random

class ReconnectingWebSocket:
    def __init__(self, url, on_message, max_retries=5):
        self.url = url
        self.on_message = on_message
        self.max_retries = max_retries
        self.ws = None
        self.subscriptions = []

    def connect_with_backoff(self):
        base_delay = 1
        max_delay = 60

        for attempt in range(self.max_retries):
            try:
                self._connect()
                self._resubscribe()
                return True
            except Exception as e:
                if attempt == self.max_retries - 1:
                    raise

                # Exponential backoff with jitter
                delay = min(base_delay * (2 ** attempt), max_delay)
                jitter = random.uniform(0, delay * 0.1)
                sleep_time = delay + jitter

                print(f"Reconnect attempt {attempt + 1} failed. Retrying in {sleep_time:.2f}s")
                time.sleep(sleep_time)

        return False

    def _connect(self):
        # Implement actual connection logic
        pass

    def _resubscribe(self):
        # Re-send all subscription messages after reconnect
        for subscription in self.subscriptions:
            self.ws.send(json.dumps(subscription))

Important: Schema Change Alert

The price_change message format was updated on September 15, 2025. The new format includes price_changes array with best_bid and best_ask fields per asset. Update your parsers accordingly.

Real-World Use Cases

1. Real-Time Price Dashboard

class PriceDashboard:
    def __init__(self):
        self.prices = {}

    def on_price_change(self, message):
        for change in message.get("price_changes", []):
            asset_id = change["asset_id"]
            best_bid = float(change["best_bid"])
            best_ask = float(change["best_ask"])
            mid_price = (best_bid + best_ask) / 2

            self.prices[asset_id] = {
                "bid": best_bid,
                "ask": best_ask,
                "mid": mid_price,
                "spread": best_ask - best_bid,
                "updated": time.time()
            }

            # Trigger UI update
            self.update_display(asset_id)

2. Whale Trade Alerts

WHALE_THRESHOLD = 10000  # $10,000 minimum

def on_trade(message):
    if message.get("event_type") == "last_trade_price":
        price = float(message["price"])
        size = float(message["size"])
        trade_value = price * size

        if trade_value >= WHALE_THRESHOLD:
            send_alert({
                "type": "WHALE_TRADE",
                "asset": message["asset_id"],
                "side": message["side"],
                "price": price,
                "size": size,
                "value": trade_value,
                "timestamp": message["timestamp"]
            })

3. Order Book Depth Analysis

def analyze_orderbook(book_event):
    bids = book_event.get("bids", [])
    asks = book_event.get("asks", [])

    if not bids or not asks:
        return None

    # Calculate spread
    best_bid = float(bids[0]["price"])
    best_ask = float(asks[0]["price"])
    spread = best_ask - best_bid

    # Calculate depth at top 5 levels
    bid_depth = sum(float(b["size"]) for b in bids[:5])
    ask_depth = sum(float(a["size"]) for a in asks[:5])

    # Detect imbalance (useful for trading signals)
    total_depth = bid_depth + ask_depth
    imbalance = bid_depth / total_depth if total_depth > 0 else 0.5

    return {
        "spread": spread,
        "spread_pct": (spread / best_bid) * 100,
        "bid_depth": bid_depth,
        "ask_depth": ask_depth,
        "imbalance": imbalance,  # >0.5 = more buying pressure
        "signal": "bullish" if imbalance > 0.6 else "bearish" if imbalance < 0.4 else "neutral"
    }

Performance Optimization

Handle High Message Volume

import asyncio
from collections import deque

class MessageProcessor:
    def __init__(self):
        self.queue = deque(maxlen=10000)
        self.running = True

    async def process_loop(self):
        while self.running:
            batch = []
            # Collect messages for 100ms
            deadline = time.time() + 0.1

            while time.time() < deadline and self.queue:
                batch.append(self.queue.popleft())

            if batch:
                await self.process_batch(batch)
            else:
                await asyncio.sleep(0.01)

    async def process_batch(self, messages):
        # Group by type for efficient processing
        by_type = {}
        for msg in messages:
            event_type = msg.get("event_type", "unknown")
            if event_type not in by_type:
                by_type[event_type] = []
            by_type[event_type].append(msg)

        # Process each type
        for event_type, msgs in by_type.items():
            await self.handle_type(event_type, msgs)

    def on_message(self, message):
        # Quick queue, don't block WebSocket thread
        self.queue.append(message)

Memory-Efficient Data Storage

from collections import deque

class MarketDataBuffer:
    def __init__(self, max_trades=1000, max_books=100):
        self.trades = deque(maxlen=max_trades)
        self.orderbooks = {}  # Only keep latest per asset

    def add_trade(self, trade):
        self.trades.append({
            "price": trade["price"],
            "size": trade["size"],
            "side": trade["side"],
            "ts": trade["timestamp"]
        })

    def update_orderbook(self, asset_id, book):
        # Only store top 10 levels to save memory
        self.orderbooks[asset_id] = {
            "bids": book["bids"][:10],
            "asks": book["asks"][:10],
            "ts": book["timestamp"]
        }

Common Issues and Solutions

Connection Drops

  • Cause: Missing heartbeat, network issues, server maintenance
  • Solution: Implement ping/pong, exponential backoff reconnection, re-subscribe after reconnect

Missing Messages

  • Cause: Gap in message sequence during reconnection
  • Solution: Request full order book snapshot after reconnect, track message sequences

Rate Limiting

  • Cause: Too many subscriptions or connections
  • Solution: Max 500 instruments per CLOB connection, use connection pooling for more

Libraries and Resources

Official Libraries

  • Python: py-clob-client - pip install py-clob-client
  • TypeScript: @polymarket/real-time-data-client - npm install @polymarket/real-time-data-client
  • TypeScript: @polymarket/clob-client - npm install @polymarket/clob-client

Community Libraries

  • TypeScript: @nevuamarkets/poly-websockets - Auto-reconnection, rate limiting
  • Rust: polymarket-rtds - High-performance Rust client
  • Go: go-polymarket-real-time-data-client

Documentation Links

  • CLOB WebSocket Overview: docs.polymarket.com/developers/CLOB/websocket/wss-overview
  • RTDS Overview: docs.polymarket.com/developers/RTDS/RTDS-overview
  • Market Channel: docs.polymarket.com/developers/CLOB/websocket/market-channel
  • User Channel: docs.polymarket.com/developers/CLOB/websocket/user-channel
  • Authentication: docs.polymarket.com/developers/CLOB/authentication

Build Faster with PolyTrack

PolyTrack uses these exact WebSocket techniques to deliver real-time whale alerts, price notifications, and portfolio tracking. Start your free trial and see how professional traders monitor Polymarket.

Next Steps

Now that you understand WebSocket streaming, you're ready to build production applications:

Frequently Asked Questions

Polymarket offers two WebSocket services: CLOB WebSocket (wss://ws-subscriptions-clob.polymarket.com) for order book and trading data, and RTDS (wss://ws-live-data.polymarket.com) for broader market data, prices, and activity feeds.

12,400+ TRADERS

Stop Guessing. Start Following Smart Money.

Get instant alerts when whales make $10K+ trades. Track P&L, win rates, and copy winning strategies.

Track Whales FreeNo credit card required