RapidOddsAPIRapidOddsAPI
Home/Blog/Guide

How to stream bookmaker odds with WebSocket

A practical guide to receiving live bookmaker odds the moment they change, instead of polling a REST endpoint on a timer.

Guide · Updated June 2026

A WebSocket odds feed pushes fresh bookmaker prices to your app as soon as each scrape cycle finishes, so you react to line moves in real time without polling. With RapidOddsAPI you connect once to wss://api.rapidoddsapi.com/ws, subscribe to the sports and bookmakers you want, and receive an odds_update message every time those prices refresh. WebSocket access is included on the Pro and Elite plans.

If you are building anything that reacts to odds, an arbitrage scanner, a positive expected value finder, a line movement tracker, the hardest part is not the maths. It is getting fresh prices fast enough to act on them. This guide walks through how to stream live bookmaker odds over a WebSocket so your app always works off the latest numbers.

The short version of why streaming helps: instead of polling a REST endpoint on a timer and hoping you caught the latest price, the server sends you new odds the moment they are available, with no wasted calls. We weigh that trade-off in full in REST vs WebSocket for odds data.

How the RapidOddsAPI feed works

The feed is event driven. Our scrapers run continuously, and each time a scrape cycle completes for a sport, every client subscribed to that sport is sent the updated prices. So the cadence follows the sport. Fast moving markets like NBA or NFL close to kickoff refresh often, quieter markets refresh less often. You always get the data as soon as it lands, rather than on a fixed clock.

The data pushed over the WebSocket has the exact same shape as the REST response, so if you already parse the REST endpoint, you do not need to change your parsing code at all.

Step 1: Connect

Connect to the WebSocket endpoint with your API key as a query parameter. You can find your key in your dashboard.

wss://api.rapidoddsapi.com/ws?api_key=your_api_key

As soon as the connection opens, the server replies with a confirmation message:

{ "event": "connected", "tier": "pro", "message": "Send subscribe messages to start receiving odds data." }

Step 2: Subscribe to a sport

Connecting does not send you any odds on its own. You tell the server what you want by sending a subscribe message with the sport, the bookmakers, and the market types you care about. Subscribe to as many sports as you like by sending one message per sport.

{ "action": "subscribe", "sport": "NBA", "bookmakers": ["Sportsbet", "DraftKings", "Pinnacle"], "market_types": ["head_to_head"] }

The server confirms the subscription and tells you how many credits each push will cost, using the same credit formula as the REST API ( market_types × ceil(bookmakers / 5)):

{ "event": "subscribed", "sport": "NBA", "bookmakers": ["Sportsbet", "DraftKings", "Pinnacle"], "market_types": ["head_to_head"], "credits_per_push": 1 }

Step 3: Receive odds updates

From here, every time prices refresh for your subscribed sport, you get an odds_update message. The data field holds the games and prices in the same structure as the REST endpoint.

{ "event": "odds_update", "sport": "NBA", "timestamp": "2026-02-25T10:00:00.000000", "credits_charged": 1, "data": { "sport": "NBA", "games": [ { "game": { "commence_time": "2026-02-25T11:00:00", "home_team": "Los Angeles Lakers", "away_team": "Boston Celtics" }, "bookmakers": [ { "name": "Pinnacle", "last_update": "2026-02-25T09:59:54", "markets": [ { "key": "head_to_head", "outcomes": [ { "name": "Los Angeles Lakers", "price": 2.10 }, { "name": "Boston Celtics", "price": 1.80 } ] } ] } ] } ] } }

A full working example in Python

Here is a complete client that connects, subscribes to NBA head to head odds across three books, and prints each update as it arrives. It uses the websockets library ( pip install websockets).

import asyncio import websockets import json API_KEY = "your_api_key" async def main(): url = f"wss://api.rapidoddsapi.com/ws?api_key={API_KEY}" async with websockets.connect(url) as ws: # 1. Connection confirmation connected = json.loads(await ws.recv()) print(connected["message"]) # 2. Subscribe await ws.send(json.dumps({ "action": "subscribe", "sport": "NBA", "bookmakers": ["Sportsbet", "DraftKings", "Pinnacle"], "market_types": ["head_to_head"] })) sub = json.loads(await ws.recv()) print(f"Subscribed. Credits per push: {sub['credits_per_push']}") # 3. Receive updates as they arrive async for message in ws: data = json.loads(message) if data["event"] == "odds_update": games = data["data"]["games"] print(f"{len(games)} games updated, " f"credits charged: {data['credits_charged']}") elif data["event"] == "error": print("Error:", data["message"]) asyncio.run(main())

The same thing in JavaScript (Node.js)

Using the ws library ( npm install ws). Run this server side so your API key stays off the client.

const WebSocket = require('ws') const API_KEY = 'your_api_key' const ws = new WebSocket(`wss://api.rapidoddsapi.com/ws?api_key=${API_KEY}`) ws.on('open', () => { ws.send(JSON.stringify({ action: 'subscribe', sport: 'NBA', bookmakers: ['Sportsbet', 'DraftKings', 'Pinnacle'], market_types: ['head_to_head'] })) }) ws.on('message', (raw) => { const msg = JSON.parse(raw) if (msg.event === 'subscribed') { console.log('Subscribed. Credits per push:', msg.credits_per_push) } if (msg.event === 'odds_update') { console.log(`${msg.data.games.length} games updated, ` + `credits charged: ${msg.credits_charged}`) } if (msg.event === 'error') { console.error('Error:', msg.message) } })

How credits work on the feed

Credits are charged per push, not per connection, so leaving a connection open all day costs nothing on its own. Each odds_update costs the credits shown in your subscribed confirmation, and you are only charged when a push actually contains data. If a scrape cycle returns no games, you are not charged. If you run out of credits when a push fires, the server sends an error event and skips that push rather than disconnecting you.

Things worth knowing

  • WebSocket is on Pro and Elite. Connecting on a plan without WebSocket access is rejected with an HTTP 403. See pricing.
  • Unsubscribe when you are done with a sport by sending { "action": "unsubscribe", "sport": "NBA" }. The server confirms with an unsubscribed event.
  • Same data as REST. The push payload matches the REST response exactly, including the point field on spreads and totals and the player_name field on player props.

Handle reconnects

Long lived connections drop sometimes. A laptop sleeps, the network blips, the server restarts. For anything running in production, wrap your client in a loop that reconnects and re subscribes whenever the socket closes, so you pick up where you left off and never miss an update. It is worth adding a short, increasing delay between attempts (back off a little after each failure) so you are not hammering the server during an outage. When the connection comes back, send your subscribe messages again, since a fresh connection starts with no subscriptions.

When REST is the better fit

Streaming is not always the right call. If you only need a snapshot now and then, or you are running a once a day job, the REST endpoint is simpler and there is nothing to keep alive. We cover the REST approach in our guide to pulling odds over REST, and weigh the two approaches directly in REST vs WebSocket for odds data.

For the full message reference, including every event type and error code, see the WebSocket documentation.

Start building with RapidOddsAPI

Real-time, standardised odds from 100+ bookmakers over REST and WebSocket. Start free with 250 credits, no credit card required.

Get Your Free API KeyRead the Docs