Webhooks

Webhooks

A webhook is a registered endpoint URL carrying one or more feed subscriptions. RaceHooks POSTs a JSON payload to that URL for every matching event during a live session. The webhook infrastructure is sport-agnostic — the same endpoint, signature verification, and filter system works across every sport RaceHooks supports. One endpoint can subscribe to many feeds (all sharing a single signing secret), each subscription can carry its own filters to narrow which events trigger a delivery, and the X-RaceHooks-Feed header tells you which feed produced each payload.

Lifecycle

Webhooks have three operational states:

activeDeliveries are sent for every matching event. This is the default state after creation.
pausedNo deliveries are sent. The webhook remains registered and can be resumed at any time without losing configuration.
deletedPermanently removed. All logs are retained for 90 days after deletion.

Create a webhook

One webhook endpoint (URL + signing secret) can subscribe to many feeds. Provide exactly one feed selection: feedId for a single feed (the simplest form), feedIds to subscribe one endpoint to several feeds at once, or subscriptions when different feeds need different filters — omitting all three returns 400. Every feed on the endpoint shares one signing secret, and the X-RaceHooks-Feed header on each delivery tells you which feed produced it. The webhookSecret is returned at creation time and retrievable anytime via GET /v1/webhooks/:id/secret.

# Subscribe one endpoint to several feeds (all share one signing secret).
# Use "feedId" for a single feed, or "subscriptions" for per-feed filters.
curl -X POST https://api.racehooks.io/v1/webhooks \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "webhookUrl": "https://your-app.com/f1",
    "feedIds": ["timing.data", "session.status"]
  }'

# Response — one endpoint carrying its per-feed subscriptions
{
  "data": {
    "webhookId": "wh_7a3b9c2d",
    "webhookUrl": "https://your-app.com/f1",
    "active": true,
    "subscriptions": [
      { "feedId": "timing.data",    "active": true },
      { "feedId": "session.status", "active": true }
    ],
    "webhookSecret": "whsec_a3f9b..."  // retrievable anytime via GET /v1/webhooks/:id/secret
  }
}
Retrieve your webhookSecret anytime via GET /v1/webhooks/:id/secret. To issue a new secret, rotate it from the webhook detail page or via POST /v1/webhooks/:id/rotate-secret. Rotation takes effect immediately.

Request fields

FieldTypeDescription
webhookUrlstringYour HTTPS endpoint URL. Must be publicly reachable. Localhost is supported during Simulate replays.
feedIdstring (one of)Subscribe to a single feed (the simplest form). See Feed catalog for all available IDs.
feedIdsstring[] (one of)Subscribe this one endpoint to many feeds at once. Supports "*" (all feeds in your tier) and group globs like "analytics.*". All feeds share one signing secret.
subscriptionsobject[] (one of)Per-feed subscriptions with their own filters: [{ feedId, filters }]. Use instead of feedIds when different feeds need different filters.
webhookMethodstring?HTTP method for deliveries. Defaults to POST. Can be PUT.
filtersobject?Optional endpoint-wide delivery filter applied to every feed. See Filters below.

Provide exactly one of feedId, feedIds, or subscriptions — omitting all three returns 400.

Filters

Filters let you narrow which events trigger a delivery. They are evaluated per payload before each delivery attempt — only matching events are sent. Filters are supported on per-driver feeds (timing.data, tire.stints, timing.driver-info, etc.) and on the events.race feed. Filters are validated strictly: an unrecognized key (e.g. a typo like driverNumber) or a wrong value type is rejected with a 400 invalid_filters error naming every violation, rather than silently delivering unfiltered.

Available filters

FieldTypeDescription
driversstring[]Three-letter driver codes (TLAs). e.g. ["VER", "NOR"]. Resolved to driver numbers at delivery time.
driverNumbersstring[]Raw driver racing numbers. e.g. ["1", "4"]. Use drivers (TLAs) for readability.
constructorsstring[]Team name keywords (case-insensitive partial match). e.g. ["ferrari", "mclaren"]. Resolved to all drivers on those teams using the current session's DriverList.
positions{ min?: number; max?: number }Race position range. Only delivers when at least one matching driver is within the specified position window.
eventTypesstring[]events.race feed only: deliver only these event types, e.g. ["overtake", "pit_entry", "fastest_lap"].
# Filter by driver TLA codes
curl -X POST https://api.racehooks.io/v1/webhooks \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "webhookUrl": "https://your-app.com/verstappen",
    "feedId": "timing.data",
    "filters": {
      "drivers": ["VER", "NOR"]
    }
  }'

# Filter by constructor keyword + position range
curl -X POST https://api.racehooks.io/v1/webhooks \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "webhookUrl": "https://your-app.com/ferrari-top5",
    "feedId": "timing.data",
    "filters": {
      "constructors": ["ferrari"],
      "positions": { "min": 1, "max": 5 }
    }
  }'
Filters are combined with AND logic: if you specify both constructors and positions, a payload is delivered only when a constructor driver is within the position range.
Constructor filters use fuzzy matching — "red bull" and "redbull" both match Red Bull Racing. The driver list is resolved live from the current session's DriverList feed, so mid-season team swaps are handled automatically.

HMAC signatures

Every delivery, on every tier — Free included — includes an X-RaceHooks-Signature header containing an HMAC-SHA256 signature of the request body. Verifying this header proves the delivery originated from RaceHooks and the body was not tampered with in transit.

X-RaceHooks-Signature: sha256=a3f9b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0

The signature is computed as:

HMAC-SHA256(webhookSecret, rawRequestBody)

Always verify against the raw request body bytes, not the parsed JSON object. Use a timing-safe comparison function (timingSafeEqual in Node.js, hmac.compare_digest in Python, subtle.ConstantTimeCompare in Go) to prevent timing attacks.

import crypto from "crypto";
import { verifySignature } from "racehooks"; // or implement manually

// Express example — use raw body middleware
app.use("/f1-hook", express.raw({ type: "application/json" }));

app.post("/f1-hook", (req, res) => {
  const sig = req.headers["x-racehooks-signature"] as string;
  const ok  = verifySignature(req.body, sig, process.env.WEBHOOK_SECRET!);

  if (!ok) return res.status(401).send("Invalid signature");

  const payload = JSON.parse(req.body.toString());
  // handle payload...
  res.sendStatus(200);
});

Rotate a secret

If a secret is compromised or you want to rotate it for security hygiene, call the rotation endpoint. The new secret takes effect immediately.

bash
curl -X POST https://api.racehooks.io/v1/webhooks/wh_7a3b9c2d/rotate-secret \
  -H "Authorization: Bearer ${TOKEN}"

# Response — new secret (also retrievable via GET /v1/webhooks/:id/secret)
{
  "data": {
    "webhookSecret": "whsec_b4c8d..."
  }
}
After rotation, the new secret is returned in the response and retrievable anytime via GET /v1/webhooks/:id/secret. Update your verification code before sending the next delivery.

Live, replay, and test deliveries

Every delivery carries an X-RaceHooks-Mode header so you can tell real session data apart from a replay or a test. It has three values:

  • live — real session data from a live session.
  • replay — a Simulate replay of a past session (you started it from the Simulate page). Carries X-RaceHooks-Replay-Session (the original session id), X-RaceHooks-Replay-Run-Id (one id shared by every event of that replay run), and X-RaceHooks-Replay-Speed.
  • test — a manual connectivity ping from the “Send test payload” button.
A replay can run at the same time as a live session. If your production pipeline should only act on real data, branch on the header — drop or route anything where X-RaceHooks-Mode is notlive. Group a replay’s events by X-RaceHooks-Replay-Run-Id to handle or discard a whole run at once.

Delivery SLA and retries

RaceHooks retries failed deliveries with exponential backoff:

AttemptDelay
1 (initial)Immediate
21 second
35 seconds
430 seconds

A delivery is considered successful when your endpoint returns a 2xx status code within 10 seconds. Any other response (timeout, 4xx, 5xx) triggers a retry.

Free and Developer are best-effort — there's no contractual delivery SLA. Custom includes a 99.5% delivery SLA, measured per calendar month, covering delivery infrastructure and excluding upstream feed availability; remedies are service credits, with full terms negotiated per contract.

← QuickstartRace Events →