Score alerts for your sports app, without polling

Teams shipping a sports, fan or team app that needs push notifications.

Register one HTTPS webhook for the leagues and event types your app cares about. Each delivery is signed; your backend verifies it and sends one push per follower through FCM or APNs. Deliveries are counted per webhook, not per app user.

Updated 2026-11-04. Data is aggregated from public sources and is typically 20-30 seconds behind live play (about 1 second after our source). There is no SLA.

Why webhooks instead of polling from the app

Polling from every installed app multiplies your API calls by your user count and drains batteries. With a webhook, the API pushes each event once to your server: event.live when a game starts, event.score_change on every score change, event.status_change, event.play and event.final. Your server decides who gets a push.

Filter by league when you register so you only pay for deliveries you use. To follow one team, check data.homeTeam.id or data.awayTeam.id in your handler; team ids are on the team API pages.

Register a webhook (once)

curl -X POST "https://www.realtimesportsapi.com/api/v1/webhooks" \
  -H "Authorization: Bearer $RSA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://api.yourapp.com/rsa-webhook","events":["event.live","event.score_change","event.final"],"leagues":["nfl","nhl"]}'
# The response includes the signing secret once. Store it as RSA_WEBHOOK_SECRET.

Verify, then fan out

Each delivery is a POST with a JSON body { event, timestamp, data }. Verify X-Webhook-Signature against the raw bytes before parsing, answer 200 quickly, then do the slow work. Failed deliveries are retried and a webhook is deactivated after 5 consecutive failures, so make the handler safe to run twice.

Node.js (Express) receiver

import express from 'express';
import { verified } from './verify.js';
import { followersOf, sendPush } from './push.js'; // your FCM / APNs code

const app = express();
app.post('/rsa-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verified(req.body, req.get('X-Webhook-Signature'), process.env.RSA_WEBHOOK_SECRET)) {
    return res.sendStatus(401);
  }
  res.sendStatus(200); // acknowledge first
  const { event, data } = JSON.parse(req.body.toString('utf8'));
  const { homeTeam: h, awayTeam: a } = data;
  const text = event === 'event.final'
    ? `Final: ${a.name} ${a.score}, ${h.name} ${h.score}`
    : `${a.name} ${a.score} - ${h.name} ${h.score}`;
  for (const teamId of [h.id, a.id]) sendPush(followersOf(teamId), text);
});
app.listen(3000);

The signature check

A timing-safe comparison of the hex HMAC-SHA256. A newer X-Webhook-Signature-V2 header with a timestamp is also sent if you want replay protection; the legacy header above is unchanged.

verify.js

import crypto from 'node:crypto';

// X-Webhook-Signature = lowercase hex HMAC-SHA256 of the raw body, no "sha256=" prefix.
export function verified(rawBody, signature, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(String(signature || ''));
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Budgeting deliveries

Every delivery counts once against your plan. A typical NFL game produces roughly a dozen events with the three types above (a few score changes, live and final); a soccer or hockey match fewer. Basketball scores change on most possessions, so subscribe to event.live and event.final only, or move up a plan. Over the monthly pool, monthly plans can opt in to overage ($2 per 1,000, capped at $100 a month by default for new opt-ins).

Test without waiting for a game: POST /api/v1/webhooks/{webhookId}/test sends a signed, production-shaped sample to your URL.

Endpoints used

  • POST /webhooks
  • GET /webhooks
  • POST /webhooks/{webhookId}/test
  • GET /sports/{sport}/leagues/{league}/teams

Full reference: docs · OpenAPI · what each endpoint returns, by league

Plans

PlanPriceCallsRateWebSocketWebhooks
Free$0125/mo (1,000 first 30 days)1/s500 msgs/moNo
Starter$29/mo ($290/yr)10,000/mo5/sShares monthly poolShares monthly pool
Growth$49/mo ($490/yr)25,000/mo10/sShares monthly poolShares monthly pool
Pro$99/mo ($990/yr)50,000/mo20/s100,000 msgs/mo25,000/mo
Business$179/mo ($1,790/yr)150,000/mo40/s300,000 msgs/mo75,000/mo
Scale$299/mo ($2,990/yr)500,000/mo100/s500,000 msgs/mo100,000/mo

Every successful REST call counts as one call. Over the limit you get HTTP 429 with a Retry-After header. No SLA. Full pricing.

FAQ

Do I pay per app user?
No. Usage is counted per webhook delivery. One score change is one delivery to your server, however many users you then notify.
Can I call the API directly from the mobile app?
Not with your key: anyone could extract it from the app bundle. Call the API from your backend and send your app the data it needs.
How quickly do alerts arrive?
Data is typically 20-30 seconds behind live play, so pushes arrive shortly after the broadcast shows the play. It is built for fan alerts, not for in-play betting.
Which events can trigger a webhook?
event.live, event.score_change, event.status_change, event.play, event.final, and the opt-in event.play_corrected, optionally filtered by league.

Complete builds to start from

Other use cases