/ws/odds channel is the raw firehose. It emits every price_change from every book we track, plus everything the /ws/live channel emits (score_change, status_change, game_final, new_game). This is the channel for arbitrage, alerting, and real-time market analysis.
Event types on this channel
Event type | When it fires |
|---|---|
welcome | Once, on connect. First frame. |
snapshot | Once, right after welcome. Game-state baseline only — no market snapshots. |
price_change | Any outcome’s price moves on any book |
score_change | Same as /ws/live |
status_change | Same as /ws/live |
game_final | Same as /ws/live |
new_game | Same as /ws/live |
snapshot frame on /ws/odds contains the same game-state baseline as /ws/live — the currently-live games and their scores/clocks. It does not contain market snapshots. For initial market state, call the REST endpoint GET /v1/games/{id}/markets for each game you care about, then stream price_change deltas from there.
Real captured sample
This is the actual wire content fromwss://books.polynode.dev/ws/odds during a live match where Estudiantes went up 2-1 (Copa Libertadores, 2H 64’):
2-1 correct-score outcome is now more likely, and we see 888sport, BetRivers, and Betano all shortening the price simultaneously (from +275/+260 to +230-240). That’s the sort of signal this channel exists to surface.
Volume expectations
During a mid-afternoon window with 5-10 games live, sustained rate is typically 100-500 price_change events per second, with bursts over 1,000/s immediately after goals or scoring plays. Plan your client-side processing accordingly.- Memory: each event is ~400 bytes JSON, so 500/s = ~200 KB/s ≈ 1.6 Mbit/s per subscriber.
- CPU: JSON parsing + any per-event logic. On a modern laptop, ~5,000 events/sec is easy; beyond that you want to batch or pre-filter.
- Storage: 500 events/sec × 86,400 seconds/day × 400 bytes ≈ 17 GB/day if you log everything. Filter aggressively or sample.
Filtering
Server-side filtering is not yet implemented in v1. Filter client-side on whichever of these fields you care about:pn_league_code(e.g., only NBA → keepnba)pn_market_type(e.g., only moneyline → keepmoneyline)book(e.g., only Polymarket vs DraftKings → keep["Polymarket", "DraftKings"])pn_slugorgame_id(e.g., only a single game)
Example: arbitrage detector
Ordering guarantees
- Within a single poll cycle,
score_changeevents are emitted beforeprice_changeevents for the same game. Clients can rely on seeing the new score before seeing the price movements that respond to it. - Across poll cycles, events are delivered in emission order per connection.
- If your consumer drops (channel full, 256 events queued), events are silently dropped, not buffered indefinitely. Detect gaps by tracking the
tsfield.
See also
- Event Reference — full schema for
price_changeand all other event types - Live Channel — if you only need game state without the odds firehose

