Self-hosted · Open source

Your team scores.
Your house reacts.

Goalfeed watches live NHL, MLB, CFL and NFL games for the teams you tell it to follow, and fires a Home Assistant event within a second of the score changing upstream — goal horn, lights, sirens, whatever you wire to it. It polls the same public league data your browser already uses and talks straight to your own Home Assistant instance. No account, no cloud, no subscription.

Self-hosted · No account · GitHub Issues only

The moment

This is what a goal looks like, arriving.

The instant a watched team's score changes, Goalfeed fires a goal event to Home Assistant, logs it, and pushes it over its WebSocket API too. Below is a simulated feed cycling through NHL and MLB — the two leagues where a score is always worth one point, so one score change is one event — showing team, score, timestamp. That's genuinely all that arrives today; see the note under the payload for why.

Live feed Watching NHL & MLB
WPG
1
TOR
2
P2 14:32
  • 20:41:03 GOAL — TOR 2–1 WPG
  • 20:36:55 PERIOD START — P2
  • 20:18:29 GOAL — WPG 1–1 TOR

Simulated for this page — not a live connection.

This is what actually reaches Home Assistant when that happens — the fields every league reliably sends:

JSON — Home Assistant goal event
{
  "id": "b3f0c9de-4b7a-4c1a-9c2e-1e5b6a2f6a10",
  "teamCode": "TOR",
  "teamName": "Toronto Maple Leafs",
  "opponentCode": "WPG",
  "opponentName": "Winnipeg Jets",
  "leagueId": 1,
  "leagueName": "NHL",
  "gameCode": "2026020123",
  "period": 2,
  "gameState": {
    "home": { "team": "TOR", "score": 2 },
    "away": { "team": "WPG", "score": 1 },
    "status": "active"
  }
}
Worth knowing

Detection is a raw score-diff, not a play-by-play feed — Goalfeed knows a team's score went up, not who scored or how. So there's no scorer name here. The full event also carries an internal type field, but it's only populated for Olympic hockey today; NHL, MLB, CFL and NFL leave it empty. Filter your automations on teamCode and leagueId, not on type.

How it works

Four small steps, once a second.

No message queue, no database, no third-party goal API. Goalfeed is one Go binary that polls, diffs, and pushes.

Poll

Every second, Goalfeed re-checks each active game for the teams you're watching — straight from NHL, MLB, CFL and NFL's own public APIs. NFL also gets a push channel, ESPN's Fastcast WebSocket, layered on top for lower latency.

Diff

It compares the new score to the last one it saw for that game. Any increase becomes an event — there's no play-by-play involved, just the number going up.

Fire

A goal event goes to Home Assistant, a matching message broadcasts over the WebSocket API, and an entry lands in the local JSONL log — all three, every time, nothing routed through a third party.

React

Your Home Assistant automation runs: goal horn, lights in team colours, a script, whatever you've built.

What "detects a score" really means

Every upstream source Goalfeed polls — NHL, MLB, CFL, NFL, and IIHF for the leagues that go further — is an unofficial, undocumented public API, the same one each league's own site uses. None of them are versioned or contractually stable, so any of them can change shape without notice. And because detection is a raw score-diff rather than a real event feed, it counts points, not plays: a converted touchdown in the NFL or CFL currently fires as seven separate goal events (one per point), not one. NHL and MLB don't have this problem — a goal is always worth one point, so one score change is one event, which is why the demo above sticks to those two. It's honest, it's simple, and it's the thing to know before you wire an automation that assumes more precision than that.

Leagues

Four leagues fire goal events today.

Two more have real code behind them but aren't part of the live polling loop or a released build yet — shown here so the status is clear, not because they're ready to use.

NHL
National Hockey League
Live
MLB
Major League Baseball
Live
CFL
Canadian Football League
Live
NFL
National Football League
Live
IIHF
Int'l Ice Hockey Federation
API only
OLY
Olympic hockey (M/W)
In development
LeagueStatusWhat that means
IIHF API only The client and service are fully built and power the web API's schedule and history lookups, but IIHF isn't registered in the live polling loop — no Home Assistant events fire for IIHF games yet.
Olympic hockey In development Wired into live goal detection in active development, but the code isn't part of a committed, released build yet. Not something you can install today.

Install

Three ways to run it.

Pick the one that matches how you already run Home Assistant. Full details, including every config option, are in the install docs and configuration reference.

The recommended path — no URL, no token, Home Assistant's Supervisor handles auth automatically.

  1. In Home Assistant: Settings → Add-ons → Add-on Store, open the menu, choose Repositories, and add:
repository url
https://github.com/goalfeed/hassio-goalfeed-repository
  1. Install the Goalfeed add-on from the store.
  2. Open its Configuration tab and enter team codes:
yaml
nhl_teams: "TOR,WPG"
mlb_teams: "TOR,NYY"
test_goals: false
Heads up

The add-on's Configuration tab currently exposes NHL and MLB only. To watch CFL or NFL teams from the add-on, mount a config.yaml or set GOALFEED_WATCH_* environment variables on the container — see the Home Assistant docs.

  1. Start the add-on — it auto-detects Home Assistant through the Supervisor API, so there's nothing to authenticate.
  2. Open the web UI from the sidebar (ingress) or http://homeassistant.local:8080.

Home Assistant

The goal horn, wired up.

Goalfeed authenticates to Home Assistant with a long-lived access token (or, on the add-on, the Supervisor handles it for you) and posts to /api/events/goal. Every event Goalfeed sends — goal detections, period-start notices, test goals — arrives under that same event type, goal, regardless of sport. Filter on the event's teamCode, not on its internal type field.

  • 01Trigger on the goal event, filtered to your team's code.
  • 02Play the goal horn on whatever media player is in the room.
  • 03Flash the lights in team colour, then bring them back.

Full walkthrough, including per-team sensors and binary sensors Goalfeed also publishes, is in the Home Assistant guide.

yaml — automations.yaml
# Winnipeg Jets goal horn
automation:
  - alias: "Goalfeed - Jets score"
    trigger:
      - platform: event
        event_type: goal
        event_data:
          teamCode: WPG
    action:
      - service: media_player.play_media
        target:
          entity_id: media_player.living_room_speaker
        data:
          media_content_id: /local/sounds/goal_horn.mp3
          media_content_type: music
      - service: light.turn_on
        target:
          entity_id: light.living_room
        data:
          color_name: blue
          flash: long
      - delay: "00:00:03"
      - service: light.turn_on
        target:
          entity_id: light.living_room
        data:
          color_name: white

For developers

Home Assistant isn't the only consumer.

Start Goalfeed with --web and you get a plain REST API, a WebSocket feed, and the bundled scoreboard UI, all on one port. Build a physical goal-light rig, a Discord bot, a browser overlay — anything that can make an HTTP request or open a socket.

REST

/api/games, /api/events, /api/teams…

Active games, historical/upcoming games by date, recent events, team lists, and Home Assistant status/config — all under ApiResponse {success, data, message}. Full reference: API docs, or Swagger UI at /swagger/index.html on your own instance.

WebSocket

ws://your-host:8080/ws

Connects immediately to a games_list snapshot, then streams game_update, event, and log messages as they happen. No auth, no ping/pong required. Full message shapes: WebSocket docs.

Worth knowing

No auth, open CORS, by design

Both APIs accept unauthenticated requests from any origin — fine behind Home Assistant ingress or on a home network, not something to expose directly to the internet. See troubleshooting if something isn't reaching you.