Self-hosted · Open source

Your team scores.
Your house reacts.

Goalfeed watches live NHL and MLB 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 empty for every league right now. 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's and MLB's own public APIs, the same ones their websites use.

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's and MLB's own public APIs — is unofficial and undocumented, the same one each league's site uses. Neither is versioned or contractually stable, so either can change shape without notice. And because detection is a raw score-diff rather than a real event feed, it counts points, not plays. For NHL and MLB that distinction doesn't bite: a goal is always worth one point, so one score change is exactly one event. It would bite for a sport where a single play is worth several points — which is part of why the football leagues aren't listed yet.

Leagues

Two leagues fire goal events today.

NHL and MLB are supported and live. CFL and NFL have client code in the repo but aren't listed below — see why underneath.

NHL
National Hockey League
Live
MLB
Major League Baseball
Live
CFL and NFL aren't listed

Client code for both leagues exists in this repo, but neither is listed as supported. Live testing on 2026-08-20 found both leagues' score tracking broken — CFL's client discards a valid payload on a type mismatch, and NFL parses a response shape ESPN doesn't send — so no goal event can fire for either today. Fixes are in progress; a league gets listed again only once it's been verified working against a live game.

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.