All posts
API ReferenceWebSocketsPolymarket APIMigration

Polymarket Replaced RTDS: Migrating to PolyBolt

On 15 September Polymarket quietly made RTDS legacy for reference prices. If you stream spot into a bot, six things changed at once — including the price source and the symbol format — and most of them fail silently rather than loudly.

10 min read

What changed, and when

On 15 September 2026 Polymarket shipped PolyBolt at wss://ws-live-v2.polymarket.com/ws and made RTDS legacy for reference prices. crypto_prices, crypto_prices_chainlink and equity_prices all map to new channels. The comments topic stays on RTDS for now.

If your bot streams spot prices to decide anything — and on crypto Up/Down markets, most do — this touches you. Six things changed at once, and the awkward part is that several of them fail quietly: you stay connected, you just stop getting the prices you think you are getting.

The six breaking changes

RTDS (legacy)PolyBolt
URLwss://ws-live-data.polymarket.comwss://ws-live-v2.polymarket.com/ws
AuthPublicCLOB API credentials required
HeartbeatClient sends PING every 5sNothing to send — server pings every 25s
Subscribe frame{action, subscriptions:[{topic,type,filters}]}{op, subscriptions:[{channel,filter}]} — filter is an object, not a string
Crypto sourceBinance / ChainlinkPyth
Symbolsbtcusdt, btc/usdbtcusd — lowercase, USD not USDT

The two that will cost you the most time

The price source changed to Pyth. Your reference price is now coming from a different oracle than it was on 14 September. If you calibrated thresholds against Binance ticks, recalibrate. And the symbol format changed — btcusdt and btc/usd are both wrong now. A bad symbol does not throw; you get a subscription that never delivers.

Channel mapping

RTDS topicPolyBolt channelFilterNote
crypto_pricesprice.crypto{"symbol":"btcusd"}Source Binance → Pyth; USDT → USD
crypto_prices_chainlinkprice.crypto{"symbol":"btcusd"}Source Chainlink → Pyth
crypto_prices_twap_sixtyprice.crypto.twap{"symbol":"btcusd","window_seconds":60}60s Chainlink TWAP
crypto_prices_twap_thirtyNone—No replacement exists
equity_pricesprice.equity{"symbol":"aapl"}Same catalog, lowercase

The 30-second TWAP is simply gone

crypto_prices_twap_thirty has no PolyBolt equivalent. That matters more than it looks: 5-minute crypto markets briefly resolved on a 30-second TWAP after 7 August, then moved to 60 seconds on 14 August. If anything in your stack still consumes the 30s window, it has no migration path — you need the 60s channel and a recalibrated model.

Connecting

Authenticate on the open connection first, wait for confirmation, then subscribe. Reference-price channels reject unauthenticated subscriptions.

{
  "op": "auth",
  "rid": "a1",
  "auth": {
    "apiKey": "<api key>",
    "secret": "<api secret>",
    "passphrase": "<api passphrase>"
  }
}

Those same credentials are the ones you derive for CLOB trading — key, secret and passphrase together. If you have never created them, the endpoint guide covers the L1/L2 flow.

Three things PolyBolt gives you that RTDS did not

  • A snapshot on every subscribe. Each subscription returns the preceding two minutes of prices with snapshot: true, possibly empty. Seed local state from it, then apply live updates. Under RTDS a quiet symbol just gave you silence and you had no idea whether you were connected or simply between ticks.
  • A dense seq and a dropped counter. You can finally detect gaps. RTDS had no sequence at all, so a missed message was invisible. Track seq per channel and treat any jump as a resync trigger.
  • Typed close codes instead of raw 1006: 4001 auth, 4002 slow consumer, 4003 draining, 4008 policy. A 4002 means you are too slow to drain the socket — that is a consumer bug, not a network problem, and reconnecting will not fix it.

Use full_accuracy_value, not value

value is a float; full_accuracy_value is an exact decimal string. On a market that settles on a TWAP comparison, float rounding at the eighth decimal is not something you want deciding a trade. Also: legacy TWAP frames used window_s and an E18 fixed-point price. PolyBolt sends a decimal string — do not apply the E18 conversion, or your prices will be off by eighteen orders of magnitude.

Connection limits

RTDS published none. PolyBolt does, and breaching any of them closes the socket with 4008:

LimitValue
Subscriptions per connection64
Subscribe frames per second20
Maximum frame size64 KB
Auth frames8

64 subscriptions is the one to plan around. If you track BTC, ETH and SOL across spot and TWAP that is six, so there is plenty of headroom for a crypto bot — but a system that fans out across the equity catalogue will hit it.

Migrating a bot, in order

  1. Derive CLOB API credentials if you do not have them. RTDS was public; PolyBolt is not. This is the step that blocks everything else.
  2. Change the URL to wss://ws-live-v2.polymarket.com/ws.
  3. Delete your client heartbeat. The 5-second PING loop is not just unnecessary, it is now noise. The server pings you every 25 seconds.
  4. Rewrite subscribe frames — action → op, topic → channel, and filter becomes a JSON object rather than a string.
  5. Fix every symbol: btcusdt and btc/usd both become btcusd. Lowercase, USD.
  6. Handle the snapshot frame so you do not treat two minutes of backfill as live ticks.
  7. Add seq gap detection and resync on a jump.
  8. Recalibrate anything tuned to Binance prices. The source is Pyth now.

If you use the TypeScript SDK

@polymarket/client 0.11.0 and later wraps this: client.subscribe([{ topic: "prices.crypto", symbols: ["btcusd"] }]) on an authenticated SecureClient, and you iterate typed events with subscribe and update types. That handles auth, framing and heartbeats for you — see the SDK guide for the wider migration picture.

What this does not change

Worth being precise, because it is easy to over-migrate. The CLOB market and user channels are untouched. wss://ws-subscriptions-clob.polymarket.com/ws/market still carries order book and price updates, still wants a client PING every 10 seconds, and still hides best_bid_ask, new_market and market_resolved behind custom_feature_enabled. Only reference prices moved. Details in the WebSocket guide.

And the deeper limitation is unchanged too: a stream gives you prices from the moment you connect. It cannot tell you what the book looked like last Tuesday. Polymarket returns 404 for a resolved market's book and keeps only about one point a minute of price history, which is why backtesting needs an archive recorded while the markets ran. See how Up/Down markets work or look up a specific market in the market lookup.

Frequently asked questions

What is PolyBolt and when did it replace RTDS?
PolyBolt is Polymarket's WebSocket for crypto, equity and TWAP reference prices, launched 15 September 2026 at wss://ws-live-v2.polymarket.com/ws. It makes RTDS legacy for reference prices: crypto_prices, crypto_prices_chainlink and equity_prices all map to new PolyBolt channels. The comments topic remains on RTDS for now.
Do I need authentication for PolyBolt?
Yes. Reference-price channels require CLOB API credentials — key, secret and passphrase — sent as an auth frame on the open connection before subscribing. This is the main operational difference from RTDS, which was public. Wait for the server to confirm authentication before sending subscribe frames.
What replaced crypto_prices_chainlink?
The price.crypto channel with filter {"symbol":"btcusd"}. Two things change beyond the name: the source moves from Chainlink to Pyth, and the symbol format changes from btc/usd to lowercase btcusd. For the 60-second Chainlink TWAP, use price.crypto.twap with window_seconds set to 60. The 30-second TWAP topic has no PolyBolt replacement.
Does PolyBolt still need a client PING?
No. Under RTDS the client sent a text PING every 5 seconds. On PolyBolt the server pings every 25 seconds and the client sends nothing, though an optional {"op":"ping"} is accepted. Note this is different from the CLOB market channel, which is unchanged and still requires a client PING every 10 seconds.
Why am I connected to PolyBolt but receiving no prices?
Almost always a symbol format problem. PolyBolt uses lowercase symbols ending in usd, so btcusdt and btc/usd are both invalid — use btcusd. A bad symbol does not raise an error; the subscription simply never delivers. Check also that authentication was confirmed before you sent the subscribe frame, and that you are not treating the initial snapshot frame as the only data.
What do the PolyBolt close codes mean?
4001 is an authentication failure, 4002 means you are a slow consumer and not draining the socket fast enough, 4003 means the server is draining, and 4008 is a policy breach such as exceeding 64 subscriptions, 20 subscribe frames per second, a 64 KB frame or 8 auth frames. A 4002 is a bug in your consumer rather than a network fault, so reconnecting without fixing throughput will just repeat it.

Get the historical data Polymarket does not keep

PolyTest records Polymarket crypto Up/Down markets as they run — 240M+ snapshots with 8 levels of order book depth, sub-second timestamps, and resolved markets preserved. Start with a 3-day free trial.

Keep reading