Polymarket-compatible WebSocket
book / price_change event consumer and
don’t want to re-key on condition_id.
Subscribe message
The subscribe key differs by channel, matching Polymarket:/ws/marketkeys by CLOB token id (assets_ids— long decimal strings, two per binary market, one per outcome). This is PM’s market channel contract./ws/userkeys by condition id (markets). This is PM’s user channel contract — “the user channel subscribes by condition IDs (market identifiers), not asset IDs.” A faithful PM SDK port sendsmarketsto its user channel.
Auth block
Alternative: pass
?token=<jwt> on connect (the polysim-native auth
model). The JWT comes from POST /v1/keys/ws-token and expires in 60
seconds. Pick exactly one auth path — passing both is fine but the
JWT takes precedence.
Events emitted
All four PM event types are emitted with PM’s exact field names:book — full L2 snapshot
price_change /
best_bid_ask — the book event is not re-emitted on every level change.
price_change — orderbook delta
Two PM deviations:
- PolySim adds a convenience top-level
asset_idthat PM’sprice_changedoes not send (PM putsmarket/price_changes[]/timestamp/event_typeat the top level; the asset id lives only inside eachprice_changes[]entry). - The inner
size,side, andhashfields are stubbed ("0"/""/""). PolySim’s price cache is a top-of-book-only payload with no per-level deltas or rolling book hash, so these are emitted for schema parity but carry no data. PM populates them.
last_trade_price — fill broadcast
best_bid_ask — top-of-book change
Two PM deviations:
- PolySim omits the
spreadfield that PM’sbest_bid_askcarries (compute it yourself asbest_ask - best_bidif you need it). - PM gates
best_bid_ask(plusnew_market/market_resolved) behindcustom_feature_enabled: truein the subscribe message. PolySim ignorescustom_feature_enabledand emitsbest_bid_askunconditionally — you do not need to set the flag.
Error frames
The PM-compat layer emits structured error frames (PM does the same):UNKNOWN_ASSET and INVALID_SUBSCRIBE are non-fatal — the connection
stays open so you can send a corrected subscribe. The three rows marked
“yes” above are terminal: the server sends the error frame, then closes
the socket so the client knows to re-handshake (with corrected auth) or
back off.
Dynamic subscribe/unsubscribe
PM SDKs mutate subscriptions without reconnecting by sending anoperation frame (the initial subscribe uses type; subsequent updates
use operation). PolySim accepts the same shape:
MAX_ASSETS_EXCEEDED error frame
(none of the new tokens are added). Send unsubscribe to free room first.
Ping/pong
PolySim accepts both heartbeat shapes:event_type (not type) on the pong frame
for consistency with the rest of the protocol.
Complete example
Python (websockets)
event_type field name and PM-style
stringified prices — work without modification.
What’s NOT in the PM-compat layer (yet)
- PM’s
orderPLACEMENT / UPDATE / CANCELLATION events on/ws/user— fill (trade) events ARE emitted: when one of your orders fills on a subscribed market,/ws/userpushes PM’s user-channeltradeframe (event_type: "trade",type: "TRADE",status: "MATCHED", unix-string timestamps). Divergences: paper trades terminate atMATCHED(noMINED/CONFIRMEDlifecycle — no chain),maker_ordersis empty, andowner/trade_ownerare empty strings. PM’sorderplacement/cancel events are still NOT emitted — pollGET /v1/data/ordersfor order state, or use the polysim-native Execution Feed (/v1/ws/executions). - L2 signature auth — PM’s full L2 contract requires
POLY_ADDRESS/POLY_SIGNATURE/POLY_TIMESTAMP/POLY_API_KEY/POLY_PASSPHRASE. PolySim is single-secret — we accept theapiKeyfield and ignore the others. SDK code that calls a PM signer to build the L2 block doesn’t need changes; the signer’s output is simply ignored.