Docs/posts/Breaking News Webhook
PUSH

Breaking News Webhook

Real-Time

The Breaking News Webhook is the push-delivery mode of GET /posts: instead of polling on a tight loop, SportFeeds delivers breaking sports news to your server the moment it's classified — usually within seconds. When a post is identified as breaking news, SportFeeds sends an HTTP POST to the endpoint you registered.

Subscriptions are managed from your dashboard under Webhooks: register a target URL, filter by league, news type, status and origin, and reveal your signing secret. This page documents the event contract your endpoint will receive.

How it works
Subscribe once, receive events automatically

1. Register a target URL and select the leagues you want in the dashboard.

2. When a post is classified as news with origin breaking, SportFeeds POSTs a signed event to your URL.

3. Your endpoint verifies the signature, responds 2xx quickly, and processes the event asynchronously.

Filtering what you receive
Subscribe only to the breaking news you care about

Each subscription supports optional filters. Leave a filter group empty to receive everything for that dimension; add values to narrow delivery. Filters combine with AND across groups and OR within a group.

  • Leagues — e.g. MLB, NBA, NFL.
  • News typesinjury, trade, signing, cut, suspension, contract_extension, roster_move, return_from_injury, coaching_change, draft, other.
  • News statusverified or rumor.
  • News originbreaking (first to report) or relaying.

Example: a subscription with leagues NFL, news types injury, and status verified receives only verified NFL injury news.

Delivery & retries

Each event is sent as a POST with Content-Type: application/json.

Requests time out after 10 seconds. Any non-2xx response (or timeout) is retried with exponential backoff (≈1m → 2m → 4m → 8m → 16m) for up to 6 attempts, after which the delivery is marked dead.

Retries reuse the same payload but carry a new X-SportFeeds-Delivery ID — de-duplicate on data.id if you need exactly-once processing.

Request headers
Sent by SportFeeds on every delivery
HeaderDescription
X-SportFeeds-EventThe event type, e.g. breaking_news.
X-SportFeeds-DeliveryUnique delivery ID for this attempt. Use it to de-duplicate retries.
X-SportFeeds-SignatureHMAC-SHA256 of the raw request body, formatted as sha256=<hex>.
Example event payload
{
  "event": "breaking_news",
  "sent_at": "2026-06-18T14:32:10.482Z",
  "data": {
    "id": "1799000000000000000",
    "post_url": "https://x.com/Source/status/1799000000000000000",
    "text": "BREAKING: Star QB ruled out for the season with a knee injury.",
    "league": "NFL",
    "media_type": "text",
    "created_at": "2026-06-18T14:32:08.000Z",
    "news_type": "injury",
    "news_status": "confirmed",
    "news_origin": "breaking",
    "news_attribution": "Beat Reporter",
    "players": [
      { "sportfeeds": 12345, "name": "Player Name" }
    ],
    "teams": [
      { "sportfeeds": 21, "name": "Team Name" }
    ],
    "event_id": "b1e7c0a2-...-9f",
    "alt_sources": [
      {
        "post_url": "https://x.com/OtherOutlet/status/1799000000000000123",
        "source": "@OtherOutlet",
        "news_attribution": "per @BeatReporter",
        "role": "alt",
        "text": "QB out for the year, per @BeatReporter.",
        "created_at": "2026-06-18T14:39:02.000Z"
      }
    ]
  }
}
Story grouping & updates
We group duplicate reports of the same story so you aren't blasted 5–10 times.

When a reporter breaks news, other outlets quickly post their own wording of the same story (often "per @reporter"). We deliver a single breaking_news event for the first report and attach the follow-ons as alt_sources. Every event carries an event_id so you can thread related deliveries.

If a later post adds key new information (e.g. the specific injury, trade compensation, or a status change), we send a breaking_news.updateevent that references the original via update_of and includes a short whats_new summary. Active breaking_news subscriptions receive these updates automatically.

{
  "event": "breaking_news.update",
  "sent_at": "2026-06-18T15:10:44.118Z",
  "data": {
    "id": "1799000000000000999",
    "post_url": "https://x.com/Insider/status/1799000000000000999",
    "text": "It's a torn ACL — QB will miss the full season.",
    "league": "NFL",
    "news_type": "injury",
    "news_status": "confirmed",
    "event_id": "b1e7c0a2-...-9f",
    "update_of": "b1e7c0a2-...-9f",
    "whats_new": "It's a torn ACL — QB will miss the full season.",
    "players": [ { "sportfeeds": 12345, "name": "Player Name" } ],
    "teams": [ { "sportfeeds": 21, "name": "Team Name" } ],
    "alt_sources": []
  }
}
Verifying signatures
Always verify before trusting a payload

Compute an HMAC-SHA256 of the raw request body using your subscription's signing secret, prefix it with sha256=, and compare it (in constant time) to the X-SportFeeds-Signature header. Reject any request that doesn't match.

import crypto from "crypto";

// Express example — make sure you have the RAW request body
function verifySportFeeds(rawBody, headerSignature, signingSecret) {
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", signingSecret).update(rawBody).digest("hex");

  // constant-time compare
  const a = Buffer.from(headerSignature || "");
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/sportfeeds/webhook", express.raw({ type: "*/*" }), (req, res) => {
  const sig = req.header("X-SportFeeds-Signature");
  if (!verifySportFeeds(req.body, sig, process.env.SPORTFEEDS_SIGNING_SECRET)) {
    return res.status(401).send("invalid signature");
  }

  const event = JSON.parse(req.body.toString("utf8"));
  // Acknowledge fast, process async
  res.status(200).send("ok");
});
Responding

Return any 2xx status as soon as you've stored the event. Do heavy processing asynchronously — slow handlers risk the 10s timeout and trigger retries.

Any other status (or no response) is treated as a failure and retried.

Testing: use the Webhooks dashboard to send a synthetic test event to your endpoint and confirm your signature verification works before going live.