Live product
Live WebSocket
Live is a separate product channel from REST. It streams entitled bookmaker updates over WebSocket after plan, source, sport, and bookmaker checks pass.
Availability and entitlement
Live is fully documented here, but it is not automatically enabled for every account. A connection needs an active API key, an active organization, live_enabled plan access, and a live-enabled bookmaker entitlement for the subscribed channel. If any of those checks fail, the server rejects the connection or subscription instead of falling back to REST.
Connect
import WebSocket from "ws";
const socket = new WebSocket("wss://stream.betspread.io", {
headers: {
Authorization: `Bearer ${process.env.BETSPREAD_API_KEY}`,
},
});
socket.on("open", () => {
socket.send(JSON.stringify({
type: "subscribe",
channel: "odds.football.tipico"
}));
});
socket.on("message", (raw) => {
console.log(JSON.parse(raw.toString()));
});Protocol contract
Endpoint
wss://stream.betspread.ioProduction Live WebSocket endpoint.
Authentication
Authorization: Bearer <API_KEY>Live currently authenticates via the WebSocket upgrade headers. Browser clients cannot set this header directly; use a trusted backend client or relay.
Channel
odds.{sport}.{bookmaker}Sport must be a public sport slug and bookmaker must be an entitled source slug, e.g. odds.football.tipico.
Plan gate
plan_limits.live_enabled = trueREST access does not imply Live access. Live must be enabled on the organization plan and bookmaker entitlement.
Replay
replayFromOptional numeric sequence string. Replay is limited by live_replay_window_seconds and bookmaker history access.
Subscribe and replay
Send a JSON message with type: "subscribe" and a channel. Add replayFrom when the client wants the server to replay recent updates after a known sequence.
{
"type": "subscribe",
"channel": "odds.football.tipico",
"replayFrom": "1779670000000"
}Messages
{
"type": "odds.update",
"sequence": "1779670123456",
"channel": "odds.football.tipico",
"sport": "football",
"bookmaker": "tipico",
"event": {
"id": "143401",
"homeTeam": "SC Paderborn 07",
"awayTeam": "VfL Wolfsburg",
"kickoffAt": "2026-05-25T18:30:00.000Z"
},
"market": {
"type": "match_result_1x2",
"line": null
},
"outcome": "away",
"odds": 2.1,
"status": "open",
"observedAt": "2026-05-25T10:58:20.000Z"
}{
"type": "subscription.rejected",
"channel": "odds.football.tipico",
"reason": "unauthorized"
}{
"type": "replay.rejected",
"channel": "odds.football.tipico",
"reason": "replay_window_exceeded"
}Failures and close codes
Connection rejected
close code 1008Missing, invalid, expired, revoked, or non-active API key.
Subscription rejected
subscription.rejectedinvalid_message, invalid_channel, or unauthorized channel/bookmaker entitlement.
Replay rejected
replay.rejectedhistory_not_entitled or replay_window_exceeded.
Server error
close code 1011Authentication or subscription handling failed server-side.
Usage metering
Live usage is metered separately from REST. Successful subscriptions record ws.subscribe, replayed messages record ws.replay, and delivered fanout updates record ws.update. Rejected subscriptions and replay attempts are still recorded with a rejected status for support and audit visibility.