> ## Documentation Index
> Fetch the complete documentation index at: https://docs.odds-api.io/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSocket API

> Connect to Odds-API.io WebSocket for instant odds updates. Subscribe to events and receive live betting odds changes with sub-second latency.

## 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.

<Note>
  **Add-on Feature**: WebSocket access is available as an add-on. Subscribe through [odds-api.io](https://odds-api.io) to enable it for your account.
</Note>

## 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

<Warning>
  The `markets` parameter is **required for the odds channel** (the default). It is not needed if you subscribe only to `scores`/`status`.
</Warning>

| Parameter  | Type            | Max | Description                                                                                                                                                                           |
| ---------- | --------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `markets`  | comma-separated | 20  | **Required for the odds channel.** Market names (e.g., `ML,Spread,Totals`)                                                                                                            |
| `channels` | comma-separated | -   | Subscription channels: `odds`, `scores`, `status`. Defaults to `odds` (see [Channels](#channels)).                                                                                    |
| `sport`    | comma-separated | 10  | Filter by sport slugs (e.g., `football,basketball`)                                                                                                                                   |
| `leagues`  | comma-separated | 20  | Filter by league slugs (e.g., `england-premier-league`)                                                                                                                               |
| `eventIds` | comma-separated | 50  | Filter by specific event IDs                                                                                                                                                          |
| `status`   | single value    | -   | `live` or `prematch`                                                                                                                                                                  |
| `lastSeq`  | integer         | -   | Resume from this sequence number. The server replays missed messages before starting the live stream. See [WebSocket Guide](/guides/websockets#reconnection-with-replay) for details. |

<Note>
  Using `leagues` or `eventIds` is recommended to reduce bandwidth. You cannot use both together.
</Note>

## 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.

<Warning>
  `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.
</Warning>

| Channel  | Default | Message types                                 | Description                                                          |
| -------- | ------- | --------------------------------------------- | -------------------------------------------------------------------- |
| `odds`   | ✓       | `created`, `updated`, `deleted`, `no_markets` | Live odds/markets (the existing stream)                              |
| `scores` |         | `score`                                       | Live score changes                                                   |
| `status` |         | `status`                                      | Match 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.

```bash theme={null}
# 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

```bash theme={null}
# 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)

| Error                                                        | Cause            |
| ------------------------------------------------------------ | ---------------- |
| `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, status`          | Unknown channel  |

## Message Types

| Type              | Channel | Description                                                                                                                                                                    |
| ----------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `welcome`         | —       | Connection confirmed with active filters                                                                                                                                       |
| `created`         | odds    | New match added                                                                                                                                                                |
| `updated`         | odds    | Match or market changed                                                                                                                                                        |
| `deleted`         | odds    | Match removed                                                                                                                                                                  |
| `no_markets`      | odds    | Match exists but currently no markets available                                                                                                                                |
| `score`           | scores  | Live score changed                                                                                                                                                             |
| `status`          | status  | Match status changed to `pending` (new match added), `live`, `settled`, or `cancelled`                                                                                         |
| `resync_required` | —       | Replay cannot be served (too large or too old). Client must rebuild from REST snapshot. See [WebSocket Guide](/guides/websockets#replay-limits-and-resync) for recovery steps. |

### Welcome Message

Sent immediately after connection:

```json theme={null}
{
  "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`.

```json theme={null}
{
  "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.

```json theme={null}
{
  "type": "score",
  "id": "63017989",
  "timestamp": 1723992773,
  "scores": { "home": 2, "away": 1, "periods": { "p1": { "home": 1, "away": 0 } } }
}
```

```json theme={null}
{
  "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:

| Key                | Meaning                                             |
| ------------------ | --------------------------------------------------- |
| `p1`, `p2`, … `pN` | Period / set / quarter scores                       |
| `ft`               | Full-time / regulation result                       |
| `ot`               | Overtime / extra time / extra innings (when played) |
| `ap`               | Penalty shootout tally (when decided on penalties)  |
| `currentgame`      | Current game points (live tennis)                   |
| `map1`, `map2`, …  | Per-map scores (esports)                            |

<Note>
  `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`).
</Note>

## Quick Start

<CodeGroup>
  ```javascript Node.js theme={null}
  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");
  ```

  ```javascript Browser theme={null}
  const apiKey = 'YOUR_API_KEY';
  const params = new URLSearchParams({
    apiKey,
    markets: 'ML,Spread,Totals',
    leagues: 'england-premier-league'
  });

  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! Leagues:", data.leagues_filter);
    } else if (data.type === 'resync_required') {
      console.log("Resync required:", data.reason);
    } else {
      console.log(`${data.type}: Match ${data.id}`);
    }
  };

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

  ```python Python theme={null}
  import websocket
  import json
  import os
  from urllib.parse import urlencode

  api_key = os.environ['ODDS_API_KEY']
  params = urlencode({
      'apiKey': api_key,
      'markets': 'ML,Spread,Totals',
      'sport': 'football',
      'status': 'live'
  })

  def on_open(ws):
      print("Connecting...")

  def on_message(ws, message):
      data = json.loads(message)
      if data['type'] == 'welcome':
          print(f"Connected! Filters: {data['sport_filter']}, {data['status_filter']}")
      elif data['type'] == 'resync_required':
          print(f"Resync required: {data['reason']}")
      elif data['type'] == 'updated':
          print(f"Match {data['id']} at {data['bookie']}")

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

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

  ws = websocket.WebSocketApp(
      f"wss://api.odds-api.io/v3/ws?{params}",
      on_open=on_open,
      on_message=on_message,
      on_error=on_error,
      on_close=on_close
  )
  ws.run_forever()
  ```

  ```php PHP theme={null}
  <?php
  use WebSocket\Client;

  $apiKey = getenv('ODDS_API_KEY');
  $params = http_build_query([
      'apiKey' => $apiKey,
      'markets' => 'ML,Spread,Totals',
      'sport' => 'football',
      'status' => 'live'
  ]);

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

  while (true) {
      try {
          $data = json_decode($client->receive(), true);

          if ($data['type'] === 'welcome') {
              echo "Connected! Filters: " . implode(', ', $data['sport_filter']) . "\n";
          } elseif ($data['type'] === 'resync_required') {
              echo "Resync required: {$data['reason']}\n";
          } else {
              echo "Type: {$data['type']}, Match: {$data['id']}\n";
          }
      } catch (Exception $e) {
          echo "Error: " . $e->getMessage() . "\n";
          break;
      }
  }
  ```
</CodeGroup>

## Handling Message Types

```javascript theme={null}
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

<AccordionGroup>
  <Accordion title="Use Filters to Reduce Bandwidth">
    Use `leagues` or `eventIds` filters when you only need specific data. This reduces bandwidth and improves performance.
  </Accordion>

  <Accordion title="Implement Reconnection Logic">
    Always implement exponential backoff for reconnections to handle network issues gracefully.
  </Accordion>

  <Accordion title="Handle All Message Types">
    Handle `welcome`, `created`, `updated`, `deleted`, `no_markets`, and `resync_required` to keep your data in sync.
  </Accordion>

  <Accordion title="One Connection Per API Key">
    New connections automatically close older ones. Don't create multiple connections with the same API key.
  </Accordion>

  <Accordion title="Process Updates Asynchronously">
    If receiving many updates, process them asynchronously to avoid blocking your main thread.
  </Accordion>
</AccordionGroup>

## Benefits Over REST API

| Feature           | REST API                  | WebSocket                    |
| ----------------- | ------------------------- | ---------------------------- |
| Latency           | 100-500ms (polling)       | \<150ms (push)               |
| Request overhead  | Multiple HTTP requests    | Single persistent connection |
| Real-time updates | Manual polling            | Automatic push               |
| Bandwidth         | Higher (repeated headers) | Lower (single connection)    |
| Best for          | Batch requests            | Live updates                 |

## Get Access

<Card title="Enable WebSocket Access" icon="bolt" href="https://odds-api.io">
  Subscribe to WebSocket as an add-on through your odds-api.io account
</Card>

## Related Guides

<CardGroup cols={2}>
  <Card title="WebSocket Guide" icon="book" href="/guides/websockets">
    Detailed guide with advanced examples
  </Card>

  <Card title="Fetching Odds" icon="chart-line" href="/guides/fetching-odds">
    Learn about REST API odds fetching
  </Card>
</CardGroup>
