Webhooks

Webhooks are not available. Registration is closed and POST /v1/webhooks returns 501 not_implemented. We are not delivering webhooks, so accepting a registration would mean promising traffic that never arrives.

This page previously described retrying, per-attempt-audited delivery. That was never true — the delivery path was never wired to anything, so no webhook has ever been sent. We would rather say that plainly than leave you building a receiver for a feed that does not exist.

What works today: poll the REST API, or use the WebSocket feed at wss://api.bigballsdata.com/live for live score push on the Pro plan and above. Webhooks will be reopened only once delivery is built and verified end to end.

The rest of this page documents the interface as it is designed, and is kept as a reference for what will return. Nothing below is callable today.

Subscribing (not available)

POST /v1/webhooksbash
curl -X POST https://api.bigballsdata.com/v1/webhooks \
  -H "Authorization: Bearer bbs_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: sub_5f3a..." \
  -d '{
    "url": "https://example.com/webhooks/bbs",
    "events": ["score_update", "match_final"],
    "filters": { "sport": "football", "league_id": "epl" }
  }'
Responsejson
{
  "data": {
    "id": "whsub_5f3a8c2d...",
    "url": "https://example.com/webhooks/bbs",
    "events": ["score_update", "match_final"],
    "filters": { "sport": "football", "league_id": "epl" },
    "signing_secret": "whsec_a1b2c3d4..."
  },
  "meta": { "request_id": "..." },
  "error": null
}
signing_secret is returned only at creation. Store it immediately. Verifying signatures without it is impossible; rotating it requires creating a new subscription.

Event types

EventFires when
score_updateHome or away score changes during a live match
match_status_changeStatus transitions (scheduled → live → final)
match_finalMatch status transitions to final
odds_updateAggregated market price moves for a tracked match
event_addedA goal, card, substitution, or scoring play is added

Delivery payload

jsonjson
{
  "id": "evt_8c2d5f3a...",
  "type": "score_update",
  "created": "2026-05-15T19:43:11Z",
  "data": {
    "match_id": "match_4a2f...",
    "home": { "team_id": "team_arsenal", "score": 2 },
    "away": { "team_id": "team_liverpool", "score": 1 },
    "status": "live",
    "minute": 67
  }
}

Verifying signatures

Each request carries X-BBS-Signature in the form t=<unix_ts>,v1=<hmac_hex>. Compute the HMAC over t + '.' + raw_body using your signing secret and compare in constant time.

Node.js verificationtypescript
import crypto from 'node:crypto';

function verify(raw: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = parts.t;
  const sig = parts.v1;
  if (!t || !sig) return false;

  // Reject timestamps older than 5 minutes to prevent replay
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(t + '.' + raw)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

Retry policy

A non-2xx response triggers retry with exponential backoff: 30s, 2m, 10m, 1h, 6h. After five failed attempts the delivery is marked permanently_failed and visible in /dashboard/webhooks. Subscriptions with three consecutive permanently_failed deliveries are auto-disabled.

Each delivery attempt is auditable from the dashboard with request id, attempt number, HTTP status, response time, and replay button.

Idempotency

Webhook deliveries can fire more than once, a retry after a network blip is the most common case. Treat evt_* ids as idempotency keys on your side. The same event id is never paired with two different payloads.