Skip to main content

Odds API WebSocket (Early Access)

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

Early Access: The WebSocket is currently in early access. To enable it, contact us at hello@odds-api.io.
Pricing:
  • 2x the REST API price
  • One-time implementation fee

Connection Details

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
  • Automatic cleanup keeps your feed stable

Update Types

Messages come in as JSON objects. Each has a type field:
TypeDescription
createdNew match added
updatedMatch or market changed
deletedMatch removed
no_marketsMatch exists but currently no markets available

Message Format

{
  "type": "updated",
  "timestamp": "2025-08-18T14:52:52.983Z",
  "id": "63017989",
  "bookie": "Pinnacle",
  "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
        }
      ]
    }
  ]
}

Quick Start

JavaScript Example

const WebSocket = require('ws');

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

ws.onopen = () => {
  console.log("Connected to Odds-API WebSocket");
};

ws.onclose = () => {
  console.log("Disconnected from WebSocket");
};

ws.onerror = (err) => {
  console.error("WebSocket error:", err);
};

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

  if (data.type === "updated") {
    console.log(`Match ID: ${data.id}`);
    console.log(`Bookmaker: ${data.bookie}`);
    console.log(`Markets: ${data.markets.length}`);

    // Process each market
    data.markets.forEach(market => {
      console.log(`  ${market.name}:`, market.odds[0]);
    });
  }
};

Python Example

import websocket
import json
import os

api_key = os.environ['ODDS_API_KEY']

def on_open(ws):
    print("Connected to Odds-API WebSocket")

def on_message(ws, message):
    data = json.loads(message)

    if data['type'] == 'updated':
        print(f"Match ID: {data['id']}")
        print(f"Bookmaker: {data['bookie']}")
        print(f"Markets: {len(data['markets'])}")

        # Process each market
        for market in data['markets']:
            print(f"  {market['name']}: {market['odds'][0]}")

def on_error(ws, error):
    print(f"Error: {error}")

def on_close(ws, close_status_code, close_msg):
    print("Disconnected from WebSocket")

# Create WebSocket connection
ws = websocket.WebSocketApp(
    f"wss://api.odds-api.io/v3/ws?apiKey={api_key}",
    on_open=on_open,
    on_message=on_message,
    on_error=on_error,
    on_close=on_close
)

# Run forever
ws.run_forever()

PHP Example

<?php
use WebSocket\Client;

$apiKey = getenv('ODDS_API_KEY');
$client = new Client("wss://api.odds-api.io/v3/ws?apiKey={$apiKey}");

echo "Connected to Odds-API WebSocket\n";

// Receive messages continuously
while (true) {
    try {
        $message = $client->receive();
        $data = json_decode($message, true);

        if ($data['type'] === 'updated') {
            echo "Match ID: {$data['id']}\n";
            echo "Bookmaker: {$data['bookie']}\n";
            echo "Markets: " . count($data['markets']) . "\n";

            foreach ($data['markets'] as $market) {
                echo "  {$market['name']}: " . json_encode($market['odds'][0]) . "\n";
            }
        }
    } catch (Exception $e) {
        echo "Error: " . $e->getMessage() . "\n";
        break;
    }
}

Handling Different Update Types

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

  switch (data.type) {
    case 'created':
      console.log(`New match created: ${data.id} at ${data.bookie}`);
      // Add match to your database/UI
      break;

    case 'updated':
      console.log(`Match updated: ${data.id} at ${data.bookie}`);
      // Update existing match odds
      updateMatchOdds(data.id, data.bookie, data.markets);
      break;

    case 'deleted':
      console.log(`Match deleted: ${data.id} at ${data.bookie}`);
      // Remove match from your database/UI
      break;

    case 'no_markets':
      console.log(`No markets available for match: ${data.id}`);
      // Handle temporarily unavailable markets
      break;

    default:
      console.log('Unknown message type:', data.type);
  }
};

function updateMatchOdds(matchId, bookmaker, markets) {
  markets.forEach(market => {
    console.log(`${market.name} updated at ${market.updatedAt}:`, market.odds[0]);

    // Update your UI or database here
    // Example: store latest odds for comparison
    const odds = market.odds[0];
    if (market.name === 'ML') {
      console.log(`  Home: ${odds.home}, Draw: ${odds.draw}, Away: ${odds.away}`);
    } else if (market.name === 'Totals') {
      console.log(`  Over ${odds.hdp}: ${odds.over}, Under: ${odds.under}`);
    }
  });
}

Advanced Implementation: Live Odds Tracker

class LiveOddsTracker {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.ws = null;
    this.matches = new Map();
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
  }

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

    this.ws.onopen = () => {
      console.log('Connected to Odds-API WebSocket');
      this.reconnectAttempts = 0;
    };

    this.ws.onmessage = (event) => {
      this.handleMessage(JSON.parse(event.data));
    };

    this.ws.onclose = () => {
      console.log('WebSocket connection closed');
      this.reconnect();
    };

    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };
  }

  handleMessage(data) {
    const key = `${data.id}-${data.bookie}`;

    switch (data.type) {
      case 'created':
        this.matches.set(key, {
          id: data.id,
          bookmaker: data.bookie,
          markets: data.markets,
          timestamp: data.timestamp,
          status: 'active'
        });
        this.onMatchCreated(data);
        break;

      case 'updated':
        const existing = this.matches.get(key);
        if (existing) {
          existing.markets = data.markets;
          existing.timestamp = data.timestamp;
          this.onMatchUpdated(data, existing);
        }
        break;

      case 'deleted':
        this.matches.delete(key);
        this.onMatchDeleted(data);
        break;

      case 'no_markets':
        const match = this.matches.get(key);
        if (match) {
          match.status = 'no_markets';
          this.onNoMarkets(data);
        }
        break;
    }
  }

  // Override these methods in your implementation
  onMatchCreated(data) {
    console.log(`New match: ${data.id} at ${data.bookie}`);
  }

  onMatchUpdated(data, previousData) {
    console.log(`Updated: ${data.id} at ${data.bookie}`);

    // Detect significant odds changes
    data.markets.forEach(market => {
      const prevMarket = previousData.markets.find(m => m.name === market.name);
      if (prevMarket) {
        this.detectOddsChange(market, prevMarket);
      }
    });
  }

  onMatchDeleted(data) {
    console.log(`Deleted: ${data.id} at ${data.bookie}`);
  }

  onNoMarkets(data) {
    console.log(`No markets: ${data.id} at ${data.bookie}`);
  }

  detectOddsChange(current, previous) {
    const currentOdds = current.odds[0];
    const prevOdds = previous.odds[0];

    // Example: alert on significant home odds change
    if (currentOdds.home && prevOdds.home) {
      const change = parseFloat(currentOdds.home) - parseFloat(prevOdds.home);
      if (Math.abs(change) > 0.1) {
        console.log(`⚠️  Significant odds change in ${current.name}:`);
        console.log(`   Home: ${prevOdds.home}${currentOdds.home}`);
      }
    }
  }

  reconnect() {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++;
      const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);

      console.log(`Reconnecting in ${delay}ms... (Attempt ${this.reconnectAttempts})`);

      setTimeout(() => this.connect(), delay);
    } else {
      console.error('Max reconnection attempts reached');
    }
  }

  getMatchOdds(matchId, bookmaker) {
    return this.matches.get(`${matchId}-${bookmaker}`);
  }

  getAllMatches() {
    return Array.from(this.matches.values());
  }

  disconnect() {
    if (this.ws) {
      this.ws.close();
    }
  }
}

// Usage
const tracker = new LiveOddsTracker(process.env.ODDS_API_KEY);
tracker.connect();

// Get all tracked matches
setTimeout(() => {
  const matches = tracker.getAllMatches();
  console.log(`\nTracking ${matches.length} matches`);
}, 5000);

React Integration

import { useEffect, useState, useRef } from 'react';

function useLiveOdds(apiKey) {
  const [odds, setOdds] = useState({});
  const [connected, setConnected] = useState(false);
  const ws = useRef(null);

  useEffect(() => {
    ws.current = new WebSocket(
      `wss://api.odds-api.io/v3/ws?apiKey=${apiKey}`
    );

    ws.current.onopen = () => {
      setConnected(true);
      console.log('Connected to WebSocket');
    };

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

      if (data.type === 'updated' || data.type === 'created') {
        setOdds(prevOdds => ({
          ...prevOdds,
          [`${data.id}-${data.bookie}`]: {
            id: data.id,
            bookmaker: data.bookie,
            markets: data.markets,
            timestamp: data.timestamp
          }
        }));
      } else if (data.type === 'deleted') {
        setOdds(prevOdds => {
          const newOdds = { ...prevOdds };
          delete newOdds[`${data.id}-${data.bookie}`];
          return newOdds;
        });
      }
    };

    ws.current.onclose = () => {
      setConnected(false);
      console.log('Disconnected from WebSocket');
    };

    return () => {
      if (ws.current) {
        ws.current.close();
      }
    };
  }, [apiKey]);

  return { odds, connected };
}

// Component
function LiveOddsDisplay() {
  const { odds, connected } = useLiveOdds(process.env.REACT_APP_ODDS_API_KEY);

  return (
    <div>
      <div className={`status ${connected ? 'connected' : 'disconnected'}`}>
        {connected ? '🟢 Connected' : '🔴 Disconnected'}
      </div>

      <div className="matches">
        {Object.values(odds).map(match => (
          <div key={`${match.id}-${match.bookmaker}`} className="match">
            <h3>Match #{match.id} - {match.bookmaker}</h3>
            {match.markets.map(market => (
              <div key={market.name} className="market">
                <strong>{market.name}:</strong>
                <pre>{JSON.stringify(market.odds[0], null, 2)}</pre>
              </div>
            ))}
          </div>
        ))}
      </div>
    </div>
  );
}

Best Practices

Always implement exponential backoff for reconnections to handle network issues gracefully. The connection is stable, but networks can drop.
Don’t just listen for updated - also handle created, deleted, and no_markets to keep your data in sync.
Keep track of match data in memory or database to compare updates and detect significant changes.
Remember that 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)
Server loadHigherLower
Best forBatch requestsLive updates

Get Access

Request WebSocket Access

Contact us at hello@odds-api.io to enable WebSocket access for your account

Next Steps

I