Skip to main content

Overview

The WebSocket feed delivers real-time odds updates with the same response format as the REST API /odds endpoint. Instead of polling, you receive instant updates whenever odds change.
Add-on Feature: WebSocket access is available as an add-on. Subscribe through odds-api.io to enable it for your account.

Connection

Endpoint:
wss://api.odds-api.io/v3/ws?apiKey=YOUR_API_KEY
Authentication:
  • API key passed as query parameter
  • One connection per API key
  • New connections automatically close older ones
Pricing: 2x the REST API price Bookmakers: The WebSocket automatically sends updates for all bookmakers you have selected in your account. Manage your bookmakers via the /bookmakers/selected/select endpoint.

Filter Parameters

The markets parameter is required for the odds channel (the default). It is not needed if you subscribe only to scores/status.
ParameterTypeMaxDescription
marketscomma-separated20Required for the odds channel. Market names (e.g., ML,Spread,Totals)
channelscomma-separated-Subscription channels: odds, scores, status. Defaults to odds (see Channels).
sportcomma-separated10Filter by sport slugs (e.g., football,basketball)
leaguescomma-separated20Filter by league slugs (e.g., england-premier-league)
eventIdscomma-separated50Filter by specific event IDs
statussingle value-live or prematch
lastSeqinteger-Resume from this sequence number. The server replays missed messages before starting the live stream. See WebSocket Guide for details.
Using leagues or eventIds is recommended to reduce bandwidth. You cannot use both together.

Channels

Use channels to choose which streams you receive. Omitting it keeps the default odds stream, so existing connections are unaffected. Subscribe to any combination — for example a scoreboard app can use channels=scores,status with no odds traffic.
channels is an allowlist — you receive only the channels you list. If you want odds, you must include odds (e.g. channels=odds,scores,status); connecting with channels=scores,status stops odds delivery. Omitting channels entirely keeps the default odds-only stream.
ChannelDefaultMessage typesDescription
oddscreated, updated, deleted, no_marketsLive odds/markets (the existing stream)
scoresscoreLive score changes
statusstatusMatch status transitions (pending, live, settled, cancelled)
sport, leagues, eventIds and status filters apply to score/status messages too. These messages are event-level (no bookie, no markets) and are best-effort live state — they carry no seq and are not replayed on reconnect; fetch the current value from GET /events after reconnecting.
# Odds + live scores + status changes
wss://api.odds-api.io/v3/ws?apiKey=xxx&channels=odds,scores,status&markets=ML,Spread,Totals
# Scoreboard only (no odds traffic, no markets needed)
wss://api.odds-api.io/v3/ws?apiKey=xxx&channels=scores,status&sport=football

Example URLs

# Live football with main markets
wss://api.odds-api.io/v3/ws?apiKey=xxx&sport=football&status=live&markets=ML,Spread,Totals

# Specific leagues
wss://api.odds-api.io/v3/ws?apiKey=xxx&leagues=england-premier-league,spain-la-liga&markets=ML

# Specific events
wss://api.odds-api.io/v3/ws?apiKey=xxx&eventIds=12345,67890

# Multiple sports, prematch only
wss://api.odds-api.io/v3/ws?apiKey=xxx&sport=football,basketball&status=prematch

Error Responses (400 Bad Request)

ErrorCause
Too many sports. Maximum 10 allowed.Exceeded limit
Too many leagues. Maximum 20 allowed.Exceeded limit
Too many event IDs. Maximum 50 allowed.Exceeded limit
Too many markets. Maximum 20 allowed.Exceeded limit
Cannot use both 'leagues' and 'eventIds' filters together.Mutual exclusion
Invalid status filter. Use 'prematch' or 'live'Invalid value
Invalid channel: X. Allowed: odds, scores, statusUnknown channel

Message Types

TypeChannelDescription
welcomeConnection confirmed with active filters
createdoddsNew match added
updatedoddsMatch or market changed
deletedoddsMatch removed
no_marketsoddsMatch exists but currently no markets available
scorescoresLive score changed
statusstatusMatch status changed to pending (new match added), live, settled, or cancelled
resync_requiredReplay cannot be served (too large or too old). Client must rebuild from REST snapshot. See WebSocket Guide for recovery steps.

Welcome Message

Sent immediately after connection:
{
  "type": "welcome",
  "message": "Connected to OddsAPI WebSocket",
  "user_id": "user123",
  "bookmakers": ["Bet365", "Pinnacle"],
  "sport_filter": ["Football"],
  "leagues_filter": ["england-premier-league"],
  "event_id_filter": [],
  "status_filter": "live",
  "market_filter": ["ML", "SPREAD", "TOTALS"],
  "channels": ["odds", "scores", "status"],
  "connected_at": "2026-01-15T21:00:00Z"
}

Message Format

Each update includes a seq field — a globally unique, monotonically increasing sequence number. Track this value for gap-free reconnection via lastSeq.
{
  "type": "updated",
  "seq": 482917,
  "timestamp": 1723992773,
  "id": "63017989",
  "bookie": "SingBet",
  "url": "https://www.singbet.com/sports/football/match/63017989",
  "markets": [
    {
      "name": "ML",
      "updatedAt": "2024-01-15T10:30:00Z",
      "odds": [
        {
          "home": "1.85",
          "draw": "3.25",
          "away": "2.10",
          "max": 500
        }
      ]
    },
    {
      "name": "Totals",
      "updatedAt": "2024-01-15T10:30:00Z",
      "odds": [
        {
          "hdp": 2.5,
          "over": "1.85",
          "under": "2.10",
          "max": 500
        }
      ]
    }
  ]
}

Score & Status Messages

score (scores channel) and status (status channel) messages describe the event itself — no bookie, no markets. A score message carries only scores — it is sent on every score change and does not include a status field. A status message is sent once per status transition (a new match appearing as pending, kickoff to live, settled, cancelled) and includes the current scores, so a status-only subscriber gets the final result on settle. The scores object matches the GET /events shape.
{
  "type": "score",
  "id": "63017989",
  "timestamp": 1723992773,
  "scores": { "home": 2, "away": 1, "periods": { "p1": { "home": 1, "away": 0 } } }
}
{
  "type": "status",
  "id": "63017989",
  "timestamp": 1723992773,
  "status": "settled",
  "scores": { "home": 2, "away": 1, "periods": { "ft": { "home": 2, "away": 1 } } }
}
The top-level scores.home/away is the OT/penalty-inclusive result. The periods map may contain:
KeyMeaning
p1, p2, … pNPeriod / set / quarter scores
ftFull-time / regulation result
otOvertime / extra time / extra innings (when played)
apPenalty shootout tally (when decided on penalties)
currentgameCurrent game points (live tennis)
map1, map2, …Per-map scores (esports)
score/status messages are best-effort live state: they carry no seq and are not replayed on reconnect. After reconnecting, fetch the current value from GET /events. The full-time key is ft (the legacy feed used fulltime).

Quick Start

const WebSocket = require('ws');

const apiKey = process.env.ODDS_API_KEY;
const params = new URLSearchParams({
  apiKey,
  markets: 'ML,Spread,Totals',
  sport: 'football',
  status: 'live'
});

const ws = new WebSocket(`wss://api.odds-api.io/v3/ws?${params}`);

ws.onopen = () => console.log("Connecting...");

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);

  if (data.type === 'welcome') {
    console.log("Connected! Filters:", data.sport_filter, data.status_filter);
  } else if (data.type === 'resync_required') {
    console.log("Resync required:", data.reason);
    console.log("Fetch a REST snapshot with includeSeq=true, then reconnect with that seq.");
  } else if (data.type === 'updated') {
    console.log(`Match ${data.id} at ${data.bookie}:`, data.markets.length, "markets");
  }
};

ws.onerror = (err) => console.error("Error:", err);
ws.onclose = () => console.log("Disconnected");

Handling Message Types

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);

  switch (data.type) {
    case 'welcome':
      console.log(`Connected: ${data.message}`);
      console.log(`Filters: ${data.sport_filter}, ${data.status_filter}`);
      break;

    case 'created':
      console.log(`New match: ${data.id} at ${data.bookie}`);
      break;

    case 'updated':
      console.log(`Match updated: ${data.id} at ${data.bookie}`);
      break;

    case 'deleted':
      console.log(`Match deleted: ${data.id} at ${data.bookie}`);
      break;

    case 'no_markets':
      console.log(`No markets: ${data.id}`);
      break;

    case 'resync_required':
      console.log(`Resync required: ${data.reason}`);
      console.log('Fetch a REST snapshot with includeSeq=true, then reconnect with that seq.');
      break;
  }
};

Best Practices

Use leagues or eventIds filters when you only need specific data. This reduces bandwidth and improves performance.
Always implement exponential backoff for reconnections to handle network issues gracefully.
Handle welcome, created, updated, deleted, no_markets, and resync_required to keep your data in sync.
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

FeatureREST APIWebSocket
Latency100-500ms (polling)<150ms (push)
Request overheadMultiple HTTP requestsSingle persistent connection
Real-time updatesManual pollingAutomatic push
BandwidthHigher (repeated headers)Lower (single connection)
Best forBatch requestsLive updates

Get Access

Enable WebSocket Access

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

WebSocket Guide

Detailed guide with advanced examples

Fetching Odds

Learn about REST API odds fetching