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

Football live channel#

wss://sports.bzzoiro.com/live/football/

Auth, subscribe protocol and close codes are shared across channels — see the overview. This page documents every server frame with its real shape.

Coverage: Basic vs Full (WS+)#

Every covered match streams score, stats, positional data and odds. Matches flagged websocket_plus: true on GET /api/v2/events/live/ additionally stream action frames: individual match actions (~100 ms latency) with pitch coordinates and player info. The channel picks the best available source automatically — the subscribed frame tells you which you got:

source You receive
"basic" event, livedata (~5 s cadence), odds
"full" Everything above + action frames + a history replay on subscribe

subscribed — snapshot on subscribe#

{
  "type": "subscribed",
  "event_id": 223510,
  "source": "full",
  "event":    { "…": "same shape as the event frame below" },
  "livedata": [ "… up to 30 recent livedata frames …" ],
  "history":  [ "… recent action frames (full only) …" ],
  "odds":     { "…": "same shape as the odds frame below" }
}

event — score, clock and stats#

Sent whenever match state changes:

{
  "type": "event",
  "event_id": 223510,
  "home": { "id": 3021, "name": "Aldosivi", "short_name": "ALD" },
  "away": { "id": 3044, "name": "Gimnasia y Esgrima", "short_name": "GYE" },
  "score": { "home": 1, "away": 0 },
  "time": {
    "minute": 67, "second": 34, "display": "67'",
    "period": 2, "injury_time": null,
    "period_started_at_uts": 1785692700,
    "status": "live", "kickoff_at": "2026-08-02T17:30:00+00:00"
  },
  "stats": {
    "home": { "possession": 55, "shots_total": 11, "corners": 5, "xg": 1.42, "…": "…" },
    "away": { "possession": 45, "shots_total": 7,  "corners": 3, "xg": 0.71, "…": "…" }
  },
  "websocket_plus": true
}

Note: the clock lives under time (time.minute, time.display) — not at the top level.

livedata — ball position and situations#

Positional/situation updates, ~5 s cadence:

{
  "type": "livedata",
  "event_id": 223510,
  "uts": 1785695254,
  "side": "home",
  "situation": "dangerous_attack",
  "coordinates": [ { "x": 78.5, "y": 41.2 } ],
  "commentary": "Dangerous attack — Aldosivi"
}
  • uts — unix timestamp in seconds
  • side — "home", "away" or null
  • coordinates — an array of {x, y} points (percent of pitch length × width, attacking left→right); usually one point
  • situation values include: goal, corner, freekick, throwin, offside, goalkeeper_saved, shotoffwoodwork, dangerous_attack, attack, possession, safe

action — per-action events (Full only)#

Individual match actions with coordinates, ~100 ms after they happen:

{
  "type": "action",
  "event_id": 223510,
  "action_type": "goal",
  "x": 87.3, "y": 51.2,
  "team": "home",
  "player": { "id": 40112, "name": "T. Fernández" },
  "score": { "home": 1, "away": 0 },
  "qualifiers": [ "left_foot" ],
  "minute": 67, "second": 34, "period": 2,
  "ts": 1785695254123
}

period#

value meaning
1 first half
2 second half
3 extra time, first half
4 extra time, second half
5 penalty shoot-out

minute and second are the clock within the match, not within the period, so a 47th-minute action in the second half reads "minute": 47, "period": 2.

temp_* — provisional actions#

Three action types are provisional and always followed by a corrected frame:

provisional confirmed by
temp_goal goal, or deleted_event if it is disallowed
temp_attempt the settled attempt type (attempt_saved, miss, post, goal)
temp_save save / attempt_saved

Treat them as "this probably just happened" and never as final. A goal that VAR disallows arrives as temp_goal and then deleted_event; if you have already counted it, the second frame is your instruction to take it back.

Corrections and revisions#

Nothing is ever edited in place — a correction is a new frame:

action_type what it means
deleted_event a previously sent action did not happen; drop it
rescinded_card a card was withdrawn
contentious_referee_decision flagged as disputed; the original stands

The score field on every action frame carries the score as of that action, so it is the cheapest way to stay consistent: if your running total ever disagrees with the score on the newest frame, trust the frame.

history vs live#

On subscribe with "coverage": "full" the subscribed frame carries a history array: the recent action frames replayed so a client joining at minute 70 is not blind to the first 69. They are the same shape as live frames. Everything that arrives afterwards is live. A history replay can contain a temp_* that was already corrected — apply the corrections in order and you land in the right place.

qualifiers#

Free-form string labels attached to an action (left_foot, header, big_chance, assisted, …). They are provider vocabulary and the set is open: treat an unknown qualifier as extra information, never as an error. An action with no qualifiers sends an empty list.

action_type — the complete list#

64 values. Anything not in this table arrives as unknown_<tid>, which is the signal that the upstream vocabulary grew and ours has not caught up yet.

On the ball

tid action_type
1 pass
3 take_on
7 tackle
8 interception
9 turnover
10 save
11 claim
12 clearance
13 miss
14 post
15 attempt_saved
16 goal
38 temp_goal
39 temp_attempt
41 punch
42 good_skill
44 aerial
45 challenge
49 ball_recovery
50 dispossessed
51 error
52 keeper_pickup
53 cross_not_claimed
54 smother
56 shield_ball_opp
60 chance_missed
61 ball_touch
74 blocked_pass
83 attempted_tackle

Stoppages and restarts

tid action_type
2 offside_pass
4 foul
5 out
6 corner_awarded
27 start_delay
28 end_delay
55 offside_provoked
57 foul_throw_in
58 penalty_faced
59 keeper_sweeper
64 resume
67 fifty_fifty
68 referee_drop_ball
80 drop_of_ball

Cards and discipline

tid action_type
17 card
47 rescinded_card
65 contentious_referee_decision

Squad and setup

tid action_type
18 player_off
19 player_on
20 player_retired
21 player_returns
34 team_setup
36 player_changed_jersey
40 formation_change

Period and coverage

tid action_type
24 condition_change
25 official_change
30 period_end
32 period_start
37 collection_end
70 injury_time_announcement
75 delayed_start
76 early_end
79 coverage_interruption

Provisional and corrections

tid action_type
43 deleted_event
63 temp_save

odds — consensus odds (~30 s)#

{
  "type": "odds",
  "event_id": 223510,
  "odds": {
    "match_winner": { "home": 2.10, "draw": 3.20, "away": 3.60 },
    "over_under": { "over_15": 1.28, "under_15": 3.75, "over_25": 2.05,
                    "under_25": 1.78, "over_35": 3.90, "under_35": 1.26 },
    "btts": { "yes": 2.00, "no": 1.80 },
    "asian_handicap": [
      { "line": -0.75, "push": "half", "home": 2.28, "away": 1.66, "bookmaker_count": 8 },
      { "line": -0.5,  "push": "none", "home": 1.98, "away": 1.88, "bookmaker_count": 11 },
      { "line": -0.25, "push": "half", "home": 1.74, "away": 2.12, "bookmaker_count": 8 },
      { "line": 0.0,   "push": "full", "home": 1.52, "away": 2.54, "bookmaker_count": 9 }
    ]
  },
  "updated_at": "2026-08-02T18:37:02+00:00"
}

asian_handicap#

A list, not a fixed set of keys: a match quotes around sixteen lines at once and which ones are live moves with the score, so there is no stable key set to promise. Ascending by line, and always present — an empty list when nothing is quoted, never null.

line is home-relative. -0.25 means the home team starts a quarter of a goal down, so home is the price of that handicap and away is the price of the mirrored +0.25. Quarter lines (±0.25, ±0.75) are quoted and so are whole ones.

push is the settlement rule when the handicap-adjusted result lands exactly on the line — the half of an Asian market that a price cannot express on its own:

push Lines If the adjusted result lands on the line
"none" ±0.5, ±1.5, … Cannot happen — every bet wins or loses outright
"full" 0, ±1, ±2, … Stake returned
"half" ±0.25, ±0.75, … Stake is split across the two neighbouring lines: half returned, half settled win or lose

A rung is published only when both legs are quoted. A one-sided rung is a book that pulled the other half, not a price, and it looks exactly like value that is not there.

Prices are averaged across the books actually quoting that rung; bookmaker_count is how many. While a match is in progress the average is rebuilt from the books' own in-play rows.

The ~30 s is how often the channel checks, not how often prices move. This frame is re-sent only when the values in its odds block actually changed, so quiet stretches are normal and expected — the underlying prices are re-read on a cadence measured in tens of minutes, not seconds. See Refresh cadence.

odds_book — one bookmaker (~30 s)#

Sent only if you subscribed with bookmaker_slug. Same odds block plus "bookmaker_slug" and "bookmaker_name". Slugs: GET /api/v2/bookmakers/.

Its asian_handicap list carries that one book's own ladder and omits bookmaker_count, which would always be 1.

Again 30 s is the check interval, not the rate at which that bookmaker moves its price. This frame de-duplicates on updated_at rather than on the prices themselves, so unlike the consensus frame above it can repeat an identical odds block when we re-read the line and found it unchanged. Compare the values if you need to act only on real moves.

Other frames#

unsubscribed, pong, and error (codes listed in the overview).

View this page as Markdown · Found a mistake? Tell us