New: MCP server for Claude and other assistants· every season back to 1917 on ArchiveSee plans
Documentation / Guides

Webhooks

Live game events sent to your server: goals, penalties, period ends, starts and finals.

Instead of asking the API every minute, give us an address and we send each event to it as it happens. Webhooks come with the Live and Complete plans, up to 10 endpoints an account, each with the events it wants and, if you like, only some leagues. Add them in your account, under Webhooks.

The events

EventWhat it says
game.startedA game has begun.
goalA goal, with the scorer, the assists, the strength and the score after it.
penaltyA penalty, with the player, the minutes and the offence.
period.endedA period has ended, with the score as it stands.
game.finalA game is over, with the final score and how it ended.

How soon

  • North American top league games are read every minute while they are on, so their starts, goals, penalties, period ends and finals reach you within about a minute.
  • KHL and MHL goals and penalties are sent as each game page is read, every few minutes, mostly just after the final horn.
  • In every other league, game.started and game.final are sent when the result first shows, within about ten minutes.

What arrives

A POST with a JSON body. The game is given exactly as /v1/games/{game_id} gives it, players with their ids and names as in the API, and live where the game is followed every minute.

POST /your/endpoint
Content-Type: application/json
X-IHA-Event: goal
X-IHA-Delivery: dl_81234
X-IHA-Signature: t=1760997600,v1=5f1c...

{"id": "ev_3b8e0c...", "type": "goal", "created_at": "2026-10-20T23:40:00Z",
 "data": {"game": {"id": "gm_...", "status": "live", "home_team": {...}, "away_team": {...}, ...},
          "goal": {"period": 2, "time_seconds": 754, "side": "home", "scorer": {"id": "pl_...", "name": "..."}, "assists": [...]},
          "live": {"period": 2, "clock": "07:26", "home_goals": 2, "away_goals": 1, "home_shots": 21, "away_shots": 17}}}

The same event is never sent twice to the same endpoint; its id stays the same on every try, so you can drop a repeat.

Checking the signature

Every delivery is signed with the endpoint's secret, shown in your account. X-IHA-Signature holds the time t and v1, the HMAC-SHA256 of t, a full stop and the raw body. Work it out yourself, compare, and refuse anything older than five minutes.

// PHP
$sig = [];
foreach (explode(',', $_SERVER['HTTP_X_IHA_SIGNATURE'] ?? '') as $part) { [$k, $v] = array_pad(explode('=', $part, 2), 2, ''); $sig[$k] = $v; }
$body = file_get_contents('php://input');
$ok = isset($sig['t'], $sig['v1']) && abs(time() - (int) $sig['t']) < 300
    && hash_equals(hash_hmac('sha256', $sig['t'] . '.' . $body, $secret), $sig['v1']);

# Python
import hmac, hashlib, time
parts = dict(p.split("=", 1) for p in signature.split(","))
expected = hmac.new(secret.encode(), (parts["t"] + "." + body).encode(), hashlib.sha256).hexdigest()
ok = abs(time.time() - int(parts["t"])) < 300 and hmac.compare_digest(expected, parts["v1"])

Answering, and when it fails

  • Answer with any 2xx within 10 seconds; do the work after answering.
  • Anything else is tried again: 8 tries in about 3 days, waiting 1, 5 and 30 minutes, then 2, 6 and 12 hours, then a day.
  • A 4xx other than 408 and 429 means the address will not take it, so that delivery stops.
  • After 20 failed deliveries in a row the endpoint is turned off and we email you. Turn it back on from your account.
  • Addresses must be https and on the public internet. We do not follow redirects.

Trying it

Send a test from your account: it posts a real recent final score to the endpoint, signed like any other, with "test": true in data. Your account lists the last deliveries, with the answer each got.