Skip to main content
The WebSocket feed delivers the exact same response format as the /odds endpoint, but in real time. Instead of polling, you receive updates instantly whenever odds change. We recommend all clients move to WebSocket for efficiency and lower latency. It reduces request overhead, scales better, and gives you immediate updates for live markets.

Why Use WebSocket?

Real-Time Updates

Receive odds changes instantly without polling

Lower Latency

Sub-150ms updates for live markets

Reduced Overhead

Single persistent connection vs repeated HTTP calls

Better Scaling

Perfect for bots, live dashboards, and in-play betting apps

Access & Pricing

Add-on Feature: WebSocket access is available as an add-on. Subscribe through odds-api.io to enable it for your account.
Pricing: 2x the REST API price Bookmakers: The WebSocket automatically sends updates for all bookmakers you have selected in your account. You can manage your selected bookmakers via the /bookmakers/selected/select endpoint or through the dashboard.

Connection Details

Endpoint:
Authentication:
  • API key passed as query parameter
  • One connection per API key
  • New connections automatically close older ones
  • Automatic cleanup keeps your feed stable

Filter Parameters

The markets parameter is required for the odds channel (the default). It is not needed when subscribing only to scores/status.
Using leagues or eventIds is recommended to reduce bandwidth. You cannot use both together.

Channels

Subscribe to one or more streams with channels. Omitting it keeps the default odds stream, so existing connections are unchanged. Add scores and/or status for live event state:
channels is an allowlist: you receive only the channels you list. To keep odds, include odds (e.g. channels=odds,scores,status); channels=scores,status alone stops odds delivery.
  • odds (default): created, updated, deleted, no_markets
  • scores: score messages on every score change
  • status: status messages on pending (new match added), live, settled, cancelled (carries the current scores, so you get the final result on settle)
Live score/status messages also carry an optional clock snapshot (same shape as GET /events/live) so you can re-anchor a match-clock ticker on every event without polling. See the Live Events guide. score/status are event-level (no bookie/markets), best-effort (no seq, not replayed). See the WebSocket API reference for full details.

Example Connection URLs

Welcome Message

Upon successful connection, you’ll receive a welcome message confirming your active filters:

Error Responses

If your connection parameters are invalid, you’ll receive a 400 Bad Request with one of these errors:

Update Types

Messages come in as JSON objects. Each has a type field: deleted and no_markets messages carry an empty markets array and are always delivered, even when you subscribe with a markets= filter, so filtered clients still learn when a match goes off the board. They do respect your status=, sport= and leagues= filters, and carry the event’s date, so a removal for a pre-match fixture is not delivered on a status=live connection. no_markets means the bookmaker has no active markets for that event at all. If the bookmaker still prices the event but not in your markets= list, you receive nothing rather than a no_markets.
Replace, don’t merge. Every updated message carries the complete set of markets we currently have for that bookmaker and event, not only the ones that changed. Replace your stored markets for that event with what arrives.If you merge incoming markets into what you already hold, a market that gets suspended or withdrawn disappears from the feed but never disappears from your copy. Suspended markets are removed from the payload rather than flagged, so absence is the signal. Merging leaves you quoting prices that are no longer offered.This applies to individual lines too. When a total is decided in play (say the 0.5 line once a goal is scored), there is no per-line “suspended” event: the line is simply missing from the next message for that event and bookmaker. A replace-based client drops it automatically; no timeout heuristics are needed.

Message Format

Each message includes a seq field: a globally unique, monotonically increasing sequence number. Use this to detect gaps and to reconnect without missing updates (see Reconnection with Replay below).

Quick Start

JavaScript Example

Python Example

PHP Example

Handling Different Update Types

Advanced Implementation: Live Odds Tracker

React Integration

Reconnection with Replay

When your client disconnects and reconnects, you can resume from where you left off using the lastSeq parameter. The server will replay any messages you missed during the disconnection.

How It Works

  1. Track the seq field from every message you receive
  2. When reconnecting, pass the last seq you received as a query parameter: lastSeq=482917
  3. The server sends a burst of missed messages before resuming the live stream
  4. After the replay burst, live updates continue as normal with no gap

Connection URL with Replay

Replay Behavior

  • Compacted latest-state replay: You receive the most recent state for each eventId:bookie combination, not every intermediate tick. If odds changed multiple times while you were disconnected, you get the final value only. This is correct for state sync; trading clients who need every tick should use /odds/movements per event.
  • The replay burst is delivered as a batch of messages immediately after the welcome message
  • Messages are filtered through your active filters (sport, leagues, markets, etc.), so you only receive updates relevant to your session
  • deleted and no_markets updates are replayed as latest state. If an event was deleted while you were disconnected, you will receive the deletion during replay
  • Replay data is retained for up to 24 hours
  • Each message is delivered as its own WebSocket frame - parse every frame with JSON.parse(event.data), exactly like live updates - and the burst is sent as fast as the connection allows

Replay Limits and Resync

If the replay cannot be served reliably, the server will not silently truncate. Instead, it sends a resync_required message. This happens when:
  • Too many replay candidates (reason: "replay_limit_exceeded"): the gap between your lastSeq and the current seq produced more updates than the server will process
  • Expired replay data (reason: "replay_window_expired"): one or more replay payloads have aged out of the retention window, which would leave gaps in the catch-up
When your last_seq is outside the replay window, the server first pushes the current state of your subscription as ordinary created updates, then continues streaming live on the same connection. No REST refetch or reconnect is needed - your existing message handler applies them like any other update. resync_required is only sent when complete state cannot be guaranteed: there was nothing cached to send, or the snapshot exceeded the per-connection limit (reason: "snapshot_truncated"). In those cases refetch from REST and discard the last_seq you stored so the next reconnect starts fresh. When you receive resync_required, rebuild your state from REST:
  1. Fetch a fresh snapshot from /odds or /odds/multi with includeSeq=true
  2. Read the X-OddsAPI-Seq response header
  3. Reconnect to the WebSocket with lastSeq set to that value
  4. You are now caught up with no gap

Example: Reconnect with Replay

REST to WebSocket Handoff

You can obtain a seq cursor from REST API responses by adding includeSeq=true to the snapshot odds endpoints (/odds, /odds/multi). The current sequence number is returned in the X-OddsAPI-Seq response header.
Use this seq value as lastSeq when opening your WebSocket connection to ensure no updates are missed between the REST snapshot and the live stream.

Best Practices

Use leagues or eventIds filters when you only need specific data. This significantly reduces bandwidth and improves performance.
Use lastSeq for gap-free reconnection. Always implement exponential backoff as a fallback. The connection is stable, but networks can drop.
Handle welcome, created, updated, deleted, and no_markets to keep your data in sync.
Each updated message contains the full current market set for that event and bookmaker, so overwrite what you have stored rather than merging into it.Merging is the most common integration mistake we see. A suspended market is dropped from the payload rather than marked, so if you merge, it stays in your data forever and you keep showing a price that is no longer available.
New connections automatically close older ones. Don’t create multiple connections with the same API key.
If receiving many updates, process them asynchronously to avoid blocking your main thread.

Benefits Over REST API

Get Access

Enable WebSocket Access

Subscribe to WebSocket as an add-on through your odds-api.io account

Next Steps

Fetching Odds

Learn about REST API odds fetching

Best Practices

Optimize your implementation

Value Bets

Identify profitable opportunities

API Reference

Explore all API endpoints