Webhooks
Receive HTTPS POSTs from sportapi when matching events occur. Sign-verified, retry-safe, and suitable for production push-notification systems.
Creating a subscription
curl https://api.sportapi.io/v1/webhooks \
-X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/sportapi",
"events": ["news.breaking", "nba.scores.live"],
"filter": { "team": "LAL" }
}'Webhook event format
POST /your-endpoint HTTP/1.1
Content-Type: application/json
X-Sportapi-Signature: t=1700000000,v1=4f2a3b1c...
X-Sportapi-Event: news.breaking
X-Sportapi-Delivery: del_8f3a2b1c
{
"id": "evt_8f3a2b1c",
"event": "news.breaking",
"created_at": "2025-11-14T03:42:18Z",
"data": {
"headline": "Lakers acquire All-Star in 3-team trade",
"summary": "...",
"url": "https://espn.com/...",
"teams": ["LAL"]
}
}Signature verification
Required to prevent forged deliveries. The signature is HMAC-SHA256 of {timestamp}.${body}, using your webhook's signing secret.
import crypto from 'node:crypto';
function verify(req, secret) {
const header = req.headers['x-sportapi-signature']; // 't=...,v1=...'
const [tPart, sigPart] = header.split(',');
const ts = tPart.split('=')[1];
const sig = sigPart.split('=')[1];
const payload = `${ts}.${req.rawBody}`;
const expected = crypto.createHmac('sha256', secret)
.update(payload).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
throw new Error('Invalid signature');
}
// Reject if older than 5 minutes — prevents replay attacks
if (Date.now()/1000 - Number(ts) > 300) {
throw new Error('Stale request');
}
}Skipping signature verification is a security hole. Anyone can POST to your webhook URL and impersonate sportapi. Always verify before acting on the payload.
Retry logic
Failed deliveries (non-2xx response, timeout > 5s) retry with exponential backoff:
- 1st retry: 1 minute
- 2nd retry: 5 minutes
- 3rd retry: 30 minutes
- 4th retry: 2 hours
- 5th retry: 12 hours
- After 5 failures: subscription is marked
failedand requires manual re-enable
Best practices
- Respond with
2xxwithin 5 seconds — queue work for later if you need it - Process asynchronously; don't do downstream work inline
- Verify signatures on every request
- Handle duplicate deliveries idempotently — use the
idfield as a dedupe key - Log
X-Sportapi-Deliveryfor traceability when contacting support - Use the
filterfield to narrow events server-side rather than discarding them on receipt