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

Node.js
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.io

Production 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 = true

REST access does not imply Live access. Live must be enabled on the organization plan and bookmaker entitlement.

Replay

replayFrom

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

Subscribe with replay
{
  "type": "subscribe",
  "channel": "odds.football.tipico",
  "replayFrom": "1779670000000"
}

Messages

Live update
{
  "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"
}
Subscription rejected
{
  "type": "subscription.rejected",
  "channel": "odds.football.tipico",
  "reason": "unauthorized"
}
Replay rejected
{
  "type": "replay.rejected",
  "channel": "odds.football.tipico",
  "reason": "replay_window_exceeded"
}

Failures and close codes

Connection rejected

close code 1008

Missing, invalid, expired, revoked, or non-active API key.

Subscription rejected

subscription.rejected

invalid_message, invalid_channel, or unauthorized channel/bookmaker entitlement.

Replay rejected

replay.rejected

history_not_entitled or replay_window_exceeded.

Server error

close code 1011

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