Skip to main content
Every event on both WebSocket channels is a JSON object with a top-level type field that disambiguates the payload shape. This page documents all five event types plus the welcome handshake message.

welcome

Sent exactly once, immediately after a successful WebSocket upgrade. Always the first frame. Real captured:

snapshot

The second frame every subscriber receives, immediately after welcome and before any delta events. Contains the complete list of currently-live games with their full state. Use this as your baseline — apply all subsequent score_change / status_change / game_final / price_change events on top of this snapshot to maintain a correct live view. Emitted once, per-subscriber. Not broadcast. Not repeated. Each new connection gets its own fresh snapshot. Real captured (from the public endpoint during a Copa Libertadores match):

Why snapshot-on-connect?

Before this existed, a new subscriber on /ws/live would see nothing until the next score change fired — which could be seconds or minutes depending on the match. With the snapshot, you know the complete live state from the moment you connect, and can start rendering a scoreboard immediately. Apply events in order:
  1. Receive snapshot — initialize your in-memory view of every live game.
  2. Receive score_change / status_change / game_final events — update the matching entry in your view.
  3. On disconnect + reconnect — discard your state, wait for the new snapshot, restart from step 2.
This is the standard pattern for any polynode WebSocket that serves stateful data (scores, orderbooks, position books). Ephemeral event streams (mempool, trade prints) don’t need a snapshot because there’s no persistent state to baseline.

What snapshot does NOT contain

  • Market snapshots are not included even on /ws/odds. For initial market state, call the REST endpoint GET /v1/games/{id}/markets once per game you care about, then stream price_change deltas from there. Blasting full market state for every live game × every book × every outcome on connect would be tens of MB and pointless for most consumers.
  • Unplayed games are not included. Only games where scores.is_live = true at snapshot build time appear.
  • Final games are not included. Once a game transitions to final, it drops out of the live set immediately.

score_change

Emitted whenever score_home, score_away, period, or clock changes on any polled game. Real captured (Estudiantes 2-1 Cusco, Copa Libertadores, 64th minute):
Channels: /ws/live, /ws/odds

status_change

Emitted when a game transitions between unplayed → live → final. Fires alongside score_change when the transition happens at a boundary. Schema:
Channels: /ws/live, /ws/odds

game_final

Fires immediately after a status_change into final or ended. Provides the final score as a convenience so you don’t have to join status_change + the last score_change. Schema:
Channels: /ws/live, /ws/odds

new_game

Emitted the first time the poller sees a particular game_id. Typically fires when a game first appears on upstream’s schedule (days before start), not when it kicks off. For kickoff, watch status_change from unplayed → live. Schema:
Channels: /ws/live, /ws/odds

price_change

The core odds-movement event. Fires whenever any book’s price on any outcome changes. Real captured (888sport shortening their “Estudiantes 2:1” correct-score price from +275 to +230 as Estudiantes went up 2-1):
Channels: /ws/odds only. (This event is not delivered on /ws/live.)

pong

Response to a client-sent {"action":"ping"} JSON message. Used for application-level keepalive when WebSocket protocol Pings are stripped by intermediate proxies. Client sends:
Server replies:

Putting it together — correlating events after a goal

The real wire sequence after a goal in a soccer game:
  1. Poll cycle runs, detects is_live=true + new score.
  2. score_change fires on both channels with the new score.
  3. Multiple price_change events fire on /ws/odds as each book reprices the relevant outcomes (moneyline, correct score, totals, next goal, anytime goalscorer, etc.).
  4. Clients see the score before the price movements because the diff engine always emits state changes first.
This ordering means you can reliably implement “when a goal is scored, wait N ms and see which books moved fastest” logic without race conditions.