WebSockets#
Push-based live data over WebSocket. One addon ($3/mo at /addons/) unlocks both channels; the token is the same one you use for REST.
Hands-on guide with working clients: Connect to live WebSockets.
Channels#
| URL | Sports | Notes |
|---|---|---|
wss://sports.bzzoiro.com/live/football/ |
Football | Recommended. Unified channel: score/stats, ball position, odds; on fully-covered matches also per-action events with pitch coordinates ("WS+") |
wss://sports.bzzoiro.com/ws/live/ |
Football + tennis | Legacy multi-sport channel; tennis subscriptions go here with "sport": "tennis" |
Frame references: Football channel · Tennis channel.
Authentication#
Same API token, two transports:
wss://sports.bzzoiro.com/live/football/?token=YOUR_API_KEY
or the token subprotocol (preferred — keeps keys out of URL logs):
new WebSocket("wss://sports.bzzoiro.com/live/football/", ["token", "YOUR_API_KEY"]);
On failure the server accepts the socket, sends one error frame, then
closes:
| Close code | Error code |
Meaning |
|---|---|---|
4401 |
auth_required |
Missing/invalid token |
4402 |
subscription_required |
No active WebSocket addon |
4404 |
not_found |
Unknown WebSocket path |
Client → server protocol#
All frames are JSON:
| Frame | Effect |
|---|---|
{"action":"subscribe","event_id":N} |
Follow a match |
{"action":"subscribe","event_id":N,"bookmaker_slug":"…"} |
Also stream one bookmaker's odds for it |
{"action":"subscribe","event_id":N,"sport":"tennis"} |
Tennis (on /ws/live/ only) |
{"action":"unsubscribe","event_id":N} |
Stop following |
{"action":"ping"} |
→ {"type":"pong"} |
Limit: 10 concurrent subscriptions per socket (match and bookmaker
subscriptions counted separately; football and tennis share the budget on
/ws/live/).
Error frames#
{"type":"error","code":…,"message":…} with codes: bad_frame, bad_json,
bad_action, bad_event_id, not_tracked (match has no live coverage),
limit (subscription cap), bad_slug, unknown_bookmaker. Only the three
4xxx cases above close the socket.
Which matches are covered#
Check the REST live list: GET /api/v2/events/live/ →
live_websocket: true— subscribable on the WebSocketwebsocket_plus: true— full per-action coverage (actionframes with coordinates)
Subscribing to an uncovered match returns not_tracked.
Operational notes#
- Server pings every 25 s; unanswered pings drop the connection (~20 s timeout). Standard clients handle this automatically.
- Subscriptions don't survive reconnects — re-subscribe after reconnecting.
- No rate limit on the socket itself; connection read timeout is 1 hour of silence.
- Try real frames without code at the debug console.