WebSocket reference

The real-time push feed behind the bundled web UI: connection lifecycle, message envelope, and what actually arrives in each message — including where it differs from the repo's own docs/WEBSOCKET.md.

Minimal working example

bash — using websocat
websocat ws://localhost:8080/ws

Connecting alone produces one message immediately — a full snapshot of every active game, before you do anything else:

json
{ "type": "games_list", "data": [] }

(An empty array is the correct, expected first message if nothing is active yet — not an error.)

Connection lifecycle

  • Endpoint: ws://<host>:<port>/ws (wss:// if you terminate TLS in front of it — Goalfeed itself doesn't).
  • No authentication, all origins allowed (CheckOrigin always returns true) — same posture as the REST API, see Security.
  • On connect: the server immediately sends a games_list snapshot of every game currently in the in-memory store.
  • Thereafter: the server pushes game_update, event, games_list, and log messages as they happen. There is no polling needed on the client side.
  • No ping/pong required. The server does read incoming frames in a loop, but only to detect disconnect — nothing in the server processes message content sent by the client. This is a receive-only protocol in practice: server → client.
  • No reconnection support server-side. If the connection drops, the client must reconnect and re-request state itself (a fresh connection gets a new games_list snapshot automatically, so a naive "reconnect and listen again" strategy is enough — there's no sequence number or replay to manage).
  • Broadcasts are non-blocking. Every broadcast uses select { case hub.broadcast <- data: default: } — if the internal buffer (1024 messages) is full, the message is dropped rather than blocking the sender. A slow or stalled client can silently miss messages instead of slowing down everyone else; there's no back-pressure or delivery guarantee.

Message envelope

json
{ "type": "games_list" | "game_update" | "event" | "log", "data": { ... } }
Typedata shapeSent when
games_listarray of models.Game on connect, and whenever the active-games set changes
game_updatesingle models.Game a game's score/period/status changed
eventsingle models.Event a goal/score event fired — see the sharp edge below
logsingle models.AppLogEntry an entry was appended to the app log (event delivery result, or a state change)
Sharp edge: this doesn't match docs/WEBSOCKET.md

The repository's own docs/WEBSOCKET.md shows this example event payload:

json — from docs/WEBSOCKET.md, NOT representative
{
  "type": "event",
  "data": {
    "type": "goal",
    "description": "Goal scored by Player Name",
    "teamCode": "TOR",
    ...
  }
}

As read from the current league services, NHL, MLB, NFL, and CFL never set Event.Type or Event.Description — those fields are left at their Go zero values, "". A real event message from any of the four leagues that actually fire live goals looks like this instead:

json — what actually arrives, NHL/MLB/NFL/CFL
{
  "type": "event",
  "data": {
    "id": "...",
    "type": "",
    "description": "",
    "teamCode": "WPG",
    "teamName": "Winnipeg Jets",
    "leagueId": 1,
    "leagueName": "NHL",
    "period": 2,
    "score": { "homeScore": 2, "awayScore": 1, "homeTeam": "WPG", "awayTeam": "TOR" }
  }
}

The only league service that currently populates type and description is Olympic hockey — which is itself untracked/work-in-progress (see the leagues reference). If you're building a consumer, filter on teamCode and leagueId, not on data.type — exactly the same guidance as the Home Assistant event, since both come from the same underlying models.Event.

Field reference: event data

FieldTypePopulated for NHL/MLB/NFL/CFL?
idstringyes
typestring (EventType enum) no — always ""
descriptionstring no — always ""
teamCode / teamNamestringyes
opponentCode / opponentNamestringyes
leagueId / leagueNameint / stringyes
periodintyes
score{homeScore, awayScore, homeTeam, awayTeam}yes
detailsnested, sport-specific (down, inning, goalType, ...) partially — inconsistent across leagues, treat as best-effort

Next

Same event data reaching Home Assistant instead — Home Assistant automations. Prefer polling? The same data is available over REST.