Skip to content

Events

The SDK exposes two families of events through client.on(...): lifecycle (connection state) and data (sport events and odds).

EventPayload
connectedundefined
disconnected{ willReconnect: boolean, code: number, reason: string }
reconnecting{ attempt: number, delayMs: number }
error{ message: string, fatal: boolean }

Fires when the WebSocket has opened and the SDK has received the gateway’s initial state. After this fires, client.snapshot() is populated.

Fires when the WebSocket closes for any reason.

  • willReconnect: true if the SDK will attempt to reconnect (transient failure within retry policy).
  • code: WebSocket close code. Auth failures use 4001, 4002, 4003 — see Close codes.
  • reason: server-provided reason string, possibly empty.

Fires once per scheduled reconnect attempt — before the attempt happens.

  • attempt: 1-based counter, reset to 1 after a successful reconnection.
  • delayMs: actual delay before this attempt (after backoff + jitter).
  • fatal: true: the SDK has stopped and will not reconnect. Possible causes: invalid apiKey (close 4001/4002), protocol incompatibility, exhausted reconnect attempts (maxAttempts reached). The next user action must be disconnect() followed by a fresh createClient(...).
  • fatal: false: informational — typically a transient WebSocket-level error already being retried.
client.on('error', ({ message, fatal }) => {
if (fatal) console.error('giving up:', message)
else console.warn('transient:', message)
})
EventPayload
sportEvent:added{ sportEvent: SportEvent, receivedAt: number }
sportEvent:updated{ sportEvent: SportEvent, receivedAt: number }
sportEvent:removed{ bookmaker: Bookmaker, sportEventId: SportEventId, receivedAt: number }
odds:changed{ bookmaker, sportEventId, marketId, selectionId, quote, receivedAt }

receivedAt is Date.now() at the moment the SDK received the message — see Time semantics.

Fires once when a sport event is first observed (per bookmaker). Also fires for each existing event during initial sync — see Snapshot vs events for the pattern to ignore initial-sync emissions.

client.on('sportEvent:added', ({ sportEvent }) => {
console.log(`new: ${sportEvent.bookmaker} ${sportEvent.name}`)
})

Fires when any field of a sport event changes — metadata, market list, or any odds value. The internal store is immutable, so a price tick produces a new instance reference. Treat the payload as a full replacement.

client.on('sportEvent:updated', ({ sportEvent }) => {
// re-render the row keyed by sportEvent.id
})

Fires when a sport event is no longer reported by its source (match ended, withdrawn, etc.). The entity itself is gone — the payload carries bookmaker and sportEventId so you can clean up references.

client.on('sportEvent:removed', ({ sportEventId }) => {
cleanupRow(sportEventId)
})

Fires per selection whose quote (or order book) changed. Always fires alongside a sportEvent:updated for the parent event — pick one based on what your code needs.

client.on('odds:changed', ({
bookmaker,
sportEventId,
marketId,
selectionId,
quote, // Quote — new top-of-book
receivedAt
}) => {
// line-move detection, alerts, analytics
})

For CLOB sources, the new full order book is on the parent Selection.orderBook — re-read it via client.getSportEvent(sportEventId) if you need it.

const onUpdated = ({ sportEvent }) => { /* … */ }
client.on('sportEvent:updated', onUpdated)
// later — must use the same reference:
client.off('sportEvent:updated', onUpdated)

The ClientEventMap type drives type inference automatically — your handler parameter is correctly typed for the event you subscribed to:

client.on('odds:changed', payload => {
payload.quote.price // typed as number
payload.bookmaker // typed as Bookmaker
})