Growing Discord community — direct access to the developer, live coverage & picks. Join the Discord Join now →
Matches Leagues Predictions The Edge Money News Stats Coverage
Docs / WebSockets addon

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. It also includes Arena 3D, the 3D match viewer, at no extra cost.

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.

The same pitch-level stream behind the football channel's action frames also drives Arena 3D — watch a match in 3D, or embed the widget on your own site with a key from your dashboard.

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.

event_id on error frames. When the error concerns one match, the frame also carries event_id, so a failure can be attributed with several subscribes in flight instead of parsing it out of message:

{"type":"error","code":"not_tracked","message":"Event 999999999 not available for live stream","event_id":999999999}

It is echoed exactly as received, so bad_event_id returns the value you sent ({"event_id":"abc"}"abc", and a subscribe with none at all → null). Errors about no particular match — bad_json, bad_frame, bad_action, auth_required — omit the key entirely, so "event_id" in frame is a reliable test. Adding it is backward-compatible: the three original keys are unchanged.

A failed subscribe holds no slot. A subscribe rejected with not_tracked, bad_event_id or limit never joins the subscription set, so it consumes nothing against the cap of 10 and there is nothing to reclaim. Subscribing to uncovered matches will therefore never produce limit, however many you send.

Which matches are covered#

Check the REST live list: GET /api/v2/events/live/

  • live_websocket: true — subscribable on the WebSocket
  • websocket_plus: true — full per-action coverage (action frames 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.
View this page as Markdown · Found a mistake? Tell us