Polymarket WebSockets: Real-Time Data That Does Not Drop
Polling the order book is the most common way to burn a rate limit budget for no benefit. Here are the four streams, the subscription options nobody documents, and the failure mode where your bot receives silence and calls it calm.
If your bot polls GET /book on a timer, this page will save you most of your rate limit budget and a chunk of latency. Polymarket pushes book updates over WebSocket, and a subscription costs you one connection instead of thousands of requests.
The four streams
| Stream | URL | Carries | Auth |
|---|---|---|---|
| Market | wss://ws-subscriptions-clob.polymarket.com/ws/market | Public book, price and lifecycle updates | None |
| User | wss://ws-subscriptions-clob.polymarket.com/ws/user | Your order and trade updates | Yes |
| RTDS | wss://ws-live-data.polymarket.com | Reference prices, comments, trade activity | None |
| Sports | wss://sports-api.polymarket.com/ws | Live game status and scores | None |
Subscribing to the market channel
Connect, then send one JSON subscription message. assets_ids takes token IDs — the same ones you get from market metadata — and type must be "market".
{
"assets_ids": ["65818619657568813474341868652308942079804919287380422192892211131408793125422"],
"type": "market",
"initial_dump": true,
"level": 2,
"custom_feature_enabled": true
}| Field | Required | Default | What it does |
|---|---|---|---|
assets_ids | Yes | — | Token IDs to subscribe to |
type | Yes | — | Must be "market" |
initial_dump | No | true | Send a full book snapshot on subscribe |
level | No | 2 | Subscription level — 1, 2 or 3 |
custom_feature_enabled | No | false | Enables best_bid_ask, new_market and market_resolved events |
Keep initial_dump: true unless you have a specific reason not to. Without it you start with no book state and have to wait for incremental updates to reconstruct one, which means your bot begins life with an incomplete picture.
Message types
Every message carries an event_type. Branch on it exhaustively — an unrecognised type should be logged, not silently dropped, because that is how you find out the protocol changed.
event_type | Meaning | Needs opt-in |
|---|---|---|
book | Full order book snapshot | No |
price_change | Incremental price/level update | No |
last_trade_price | A trade executed | No |
tick_size_change | Market's tick grid changed | No |
best_bid_ask | Top-of-book update | Yes |
new_market | A market was created | Yes |
market_resolved | A market settled | Yes |
Staying connected: PING every 10 seconds
This is the requirement that catches everyone. The client must send a PING every 10 seconds; the server replies PONG. Miss the heartbeat and the connection is dropped.
Note the direction: this is a client heartbeat. You are not waiting for the server to ping you. A library's built-in WebSocket keepalive at some other interval is not a substitute — send the application-level PING yourself.
import asyncio, json, time
import websockets
URL = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
STALE_AFTER = 30 # seconds without any message = treat as dead
async def stream(token_ids, on_event):
backoff = 1
while True:
try:
async with websockets.connect(URL) as ws:
await ws.send(json.dumps({
"assets_ids": token_ids,
"type": "market",
"initial_dump": True,
"custom_feature_enabled": True, # or lose 3 event types
}))
backoff = 1 # reconnected cleanly
last_msg = time.monotonic()
async def heartbeat():
while True:
await asyncio.sleep(10) # required cadence
await ws.send("PING")
hb = asyncio.create_task(heartbeat())
try:
while True:
# Silence is a failure mode, not a quiet market.
raw = await asyncio.wait_for(ws.recv(), timeout=STALE_AFTER)
last_msg = time.monotonic()
if raw == "PONG":
continue
for msg in json.loads(raw) if raw.startswith("[") else [json.loads(raw)]:
on_event(msg)
finally:
hb.cancel()
except Exception as exc:
# Reconnect with capped exponential backoff + jitter.
wait = min(backoff, 30) * (0.5 + 0.5 * asyncio.get_event_loop().time() % 1)
print(f"ws down ({exc}); reconnecting in {wait:.1f}s")
await asyncio.sleep(wait)
backoff = min(backoff * 2, 30)Changing subscriptions without reconnecting
Five-minute markets expire constantly, so a bot tracking "current BTC 5m markets" needs to change its subscription set continuously. You do not have to tear down the connection to do it — the market channel supports subscribing and unsubscribing on a live connection.
This matters more than it sounds. Reconnecting on every market rotation means repeatedly paying connection setup cost, re-requesting initial dumps, and creating a window where you are receiving nothing at exactly the moment a new market opens.
The failure mode that costs money
A dropped WebSocket does not raise an exception. It stops delivering messages. To a naive bot, "no updates" is indistinguishable from "the market is not moving" — so a bot holding a position will sit on a stale price indefinitely, confident that nothing has changed.
- Track the age of the last message. If nothing has arrived in ~30 seconds on an active market, you are disconnected, not calm. The
asyncio.wait_fortimeout above is doing exactly this job. - Treat staleness as an error condition. Stop quoting, flatten if your risk rules require it, and reconnect — do not keep trading on a frozen book.
- Back off on reconnect, with jitter. A tight reconnect loop during an outage is how you get rate limited on top of being disconnected, and a fleet reconnecting in lockstep makes it worse.
- Re-sync state after reconnecting. You missed updates while you were away. Take the fresh
initial_dumpas truth and discard your prior book, rather than merging incremental updates into a stale snapshot. - Reconcile positions after any gap. If you were disconnected from the User channel, you may have missed a fill. Re-read positions before acting on assumed state.
WebSockets and rate limits
A frequent question, and the answer is the point of the whole exercise: a subscription does not consume per-request rate limit budget the way polling does. Polymarket's documented limits are per-endpoint HTTP limits, and streaming sidesteps them almost entirely.
| Approach | Cost of tracking 20 markets for an hour |
|---|---|
Poll /book every second | 72,000 requests against a 1,500 req/10s budget |
Poll /books (batch) every second | 3,600 requests against a 500 req/10s budget |
| WebSocket subscription | One connection, plus 360 heartbeats |
You will still make HTTP calls — market discovery, tick sizes, order placement, position reconciliation — and those remain subject to the documented rate limits. Streaming just removes the largest and least useful source of request volume.
What streams will not give you
One boundary worth stating plainly, because it is the thing people try next. A WebSocket gives you the book from the moment you subscribe. It cannot give you the book last Tuesday.
Polymarket keeps no historical order book. Once a market resolves, its book returns 404 and prices-history returns 200 with an empty array. If you want to test a strategy against how markets actually traded, you need snapshots somebody recorded at the time — PolyTest does that for crypto Up/Down markets, with 8 levels of depth per side and resolved markets retained. Or start your own collector today, accept that you begin with zero history, and wait.
Frequently asked questions
- What is the Polymarket WebSocket URL?
- The public market channel is wss://ws-subscriptions-clob.polymarket.com/ws/market and the authenticated user channel is wss://ws-subscriptions-clob.polymarket.com/ws/user. There are also two other streams: RTDS at wss://ws-live-data.polymarket.com for reference prices, comments and trade activity, and a sports feed at wss://sports-api.polymarket.com/ws.
- Is there a rate limit on the Polymarket WebSocket?
- A WebSocket subscription does not consume per-request rate limit budget the way HTTP polling does — Polymarket's documented limits are per-endpoint HTTP limits. Streaming is specifically the recommended way to avoid them. The binding requirement on a connection is the heartbeat: send a PING every 10 seconds or the connection is dropped. HTTP calls you still make for discovery, tick sizes and order placement remain subject to the normal limits.
- How do I keep a Polymarket WebSocket connection alive?
- Send a PING message every 10 seconds; the server responds with PONG. This is a client-side application-level heartbeat, so a WebSocket library's built-in keepalive at a different interval is not a substitute. Also track the age of the last received message — a dropped connection stops delivering data without raising an error, so treat roughly 30 seconds of silence on an active market as a disconnect rather than a quiet market.
- Why am I not receiving best_bid_ask or market_resolved events?
- Those events are off by default. Set custom_feature_enabled to true in your subscription message to enable best_bid_ask, new_market and market_resolved. There is no error or warning when the flag is absent — the events simply never arrive, which is easy to mistake for an inactive market.
- Should my Polymarket bot use WebSockets or poll the REST API?
- Use WebSockets for live book and price data. Tracking 20 markets by polling /book once per second costs 72,000 requests per hour against a 1,500 req/10s budget, while a subscription costs one connection plus heartbeats — and polling also returns a staler answer, since you see the book as of your last poll rather than as of the last change. Keep REST for market discovery, tick sizes, order placement and position reconciliation.
- Can I change which markets I am subscribed to without reconnecting?
- Yes. The market channel supports subscribing and unsubscribing to assets on a live connection, which matters for short-lived markets like 5-minute Up/Down contracts that rotate constantly. Reconnecting on every rotation repeatedly pays connection setup cost and leaves a gap in coverage at exactly the moment a new market opens.
Get the historical data Polymarket does not keep
PolyTest records Polymarket crypto Up/Down markets as they run — 90M+ snapshots with 8 levels of order book depth, sub-second timestamps, and resolved markets preserved. Free tier, no card.
Keep reading
How to Build a Polymarket Trading Bot
Most Polymarket bot tutorials are built on SDKs that no longer exist. This is the current path end to end — what to install in 2026, how auth actually works, where the data gaps are, and the failure modes that kill bots in week one.
Polymarket API Rate Limits: The Complete Reference
Polymarket publishes its rate limits across several pages, in two completely different systems, using a 10-second window almost nobody expects. This is all of it in one table set — plus what actually happens when you cross a limit.
The Polymarket Python SDK in 2026 (and Migrating off py-clob-client)
If your code still imports ClobClient from py_clob_client, it is built on a superseded package — and so is most of the Polymarket code an AI assistant will write for you. Here is the current SDK and how to move.