Webhooks
Register an endpoint and we deliver each confirmed event as a signed HTTP POST — with retries, replay, and HMAC signatures. Best when you'd rather receive pushes than hold a socket open.
Register an endpoint
curl -X POST https://api.firstbuzzer.com/v1/webhooks \
-H "Authorization: Bearer $FB_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://your-app.com/firstbuzzer", "sports": ["nba","nhl"], "tier": "confirmed" }' The response includes a secret — store it; you'll use it to verify signatures.
Payload & headers
POST /firstbuzzer HTTP/1.1
X-FB-Topic: event.score
X-FB-Event-Id: evt_8f21
X-FB-Delivery-Id: dlv_55a0
X-FB-Signature: t=1782693490,v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
{ "type": "score", "tier": "confirmed", "game_id": "nba_401766",
"team": "BOS", "points": 3, "ts": "2026-06-29T01:14:22.184Z",
"confidence": { "agreement": 0.94, "reporters": 7, "latency_ms": 820 },
"schema": "fb.event.v1" } Signature verification
The signature is HMAC-SHA256 over {timestamp}.{raw_body}, keyed by your endpoint secret. Always verify before trusting a payload, and reject timestamps older than ~5 minutes.
import crypto from "node:crypto";
const TOLERANCE_S = 300; // reject signatures older than 5 minutes (replay)
export function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (!parts.t || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > TOLERANCE_S) return false;
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1);
return a.length === b.length && crypto.timingSafeEqual(a, b);
} import hmac, hashlib, time
TOLERANCE_S = 300 # reject signatures older than 5 minutes (replay)
def verify(raw_body: str, header: str, secret: str) -> bool:
parts = dict(p.split("=") for p in header.split(","))
if "t" not in parts or "v1" not in parts:
return False
if abs(time.time() - int(parts["t"])) > TOLERANCE_S:
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.{raw_body}".encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"]) Retries & auto-disable
Return 2xx within 5s to acknowledge. Non-2xx or a timeout is retried with backoff:
| Attempt | Delay |
|---|---|
| 1 | immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5+ | 2 hours (up to your plan's max attempts) |
An endpoint that fails every delivery for 24 hours is auto-disabled and you're notified. Re-enable it from the dashboard once it's healthy.
Delivery statuses
| Status | Meaning |
|---|---|
pending | Queued or mid-retry. |
delivered | Your endpoint returned 2xx. |
failed | Exhausted retries. |
disabled | Endpoint auto-disabled after sustained failure. |
Replay any delivery from the dashboard or POST /v1/webhooks/:id/replay. Delivery caps are per plan.