The official racehooks npm package is a TypeScript-first SDK for the RaceHooks API. It handles OAuth token management, request retries, and HMAC signature verification — so you can focus on building. The SDK works across all sports supported by the platform; no changes are needed when IndyCar or NASCAR feeds go live.
npm install racehooks
# or
yarn add racehooks
# or
pnpm add racehooks
Quick start
index.ts
import { RaceHooks, verifySignature } from "racehooks";
const rh = new RaceHooks({
clientId: process.env.RACEHOOKS_CLIENT_ID!,
clientSecret: process.env.RACEHOOKS_CLIENT_SECRET!,
});
// Subscribe to all race events
const { webhook, webhookSecret } = await rh.webhooks.create({
feedId: "events.race",
webhookUrl: "https://yourserver.com/f1-hook",
});
// webhookSecret signs deliveries; retrievable anytime via
// GET /v1/webhooks/:id/secret (and rotatable)
console.log("Signing secret:", webhookSecret);
Verify incoming deliveries
server.ts
import { verifySignature } from "racehooks";
import express from "express";
const app = express();
app.use("/f1-hook", express.raw({ type: "application/json" }));
app.post("/f1-hook", (req, res) => {
const sig = req.headers["x-racehooks-signature"] as string;
if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET!)) {
return res.status(401).send("Bad signature");
}
const payload = JSON.parse(req.body.toString());
console.log("Event:", payload.event, payload.data);
res.sendStatus(200);
});
RaceHooks constructor
Field
Type
Description
clientId
string
Your RaceHooks OAuth client ID.
clientSecret
string
Your RaceHooks OAuth client secret.
baseUrl
string?
Override the API base URL. Defaults to https://api.racehooks.io.
timeout
number?
Request timeout in milliseconds. Defaults to 10,000.
ℹ
The SDK auto-refreshes your OAuth Bearer token 60 seconds before expiry and deduplicates concurrent token requests — no caching or token management needed on your side.
// Current subscription tier and limits
const sub = await rh.usage.subscription();
console.log(sub.tier, sub.limits.maxWebhooks);
// This month's delivery count
const today = await rh.usage.current();
console.log(today.deliveryCount, today.bucketRemaining);
// Breakdown by feed
const byFeed = await rh.usage.byFeed();
byFeed.forEach(f => console.log(f.feedId, f.deliveryCount));
// Last 90 days history
const history = await rh.usage.history();
rh.simulate
typescript
// Start a simulation
const sim = await rh.simulate.start({
sessionId: "2026-great-britain_r",
speed: 10, // 10× real-time
feeds: ["events.race", "timing.data"], // omit to replay all subscribed feeds
});
console.log("Simulation started:", sim.simulationId);
// Poll for completion
let s = await rh.simulate.get(sim.simulationId);
while (s.status === "running") {
await new Promise(r => setTimeout(r, 2000));
s = await rh.simulate.get(sim.simulationId);
}
console.log(`Done: ${s.eventsDispatched} events, ${s.deliveryCount} deliveries`);
// List and cancel
const all = await rh.simulate.list();
await rh.simulate.cancel(sim.simulationId);
rh.fantasy
Fantasy scoring for a race weekend — estimated points per driver, a live in-session running score, and a pit-lane leaderboard. These endpoints require a paid plan (Developer tier or higher). See the Fantasy guide for the scoring model.
typescript
// Estimated fantasy points for a completed race (Grand Prix by default)
const gp = await rh.fantasy.getRaceScores("2026-italy-r16");
gp.scores.forEach(s => console.log(s.tla, s.breakdown.total));
// Score the Sprint instead (compressed points table)
const sprint = await rh.fantasy.getRaceScores("2026-italy-r16", { session: "sprint" });
console.log(sprint.sessionType); // "sprint"
// Running, provisional score while a session is live
const live = await rh.fantasy.getSessionScores("2026-italy_r");
if (live.live) {
console.log(`Lap ${live.currentLap}/${live.totalLaps}`);
live.scores.forEach(s => console.log(s.tla, s.breakdown.total)); // provisional
}
// Pit-lane leaderboard for a session (fastest → slowest)
const pit = await rh.fantasy.getSessionPitTimes("2026-italy_r");
console.log("Fastest stop:", pit.fastestStop?.tla, pit.fastestStop?.pitLaneTimeSec);
✦
Building a full fantasy game? The companion racehooks-f1-fantasy-scorer package (GitHub · npm) turns events.race webhooks into a live leaderboard under official-style, DFS-style, or custom rules. Walkthrough on the Fantasy guide.
Analytics payloads
On the Custom tier, timing.data and tire.history payloads are enriched with ML-computed fields. The SDK exports a WebhookPayload type that includes the analytics key.
typescript
import type { WebhookPayload } from "racehooks";
// On the Custom tier, timing.data / tire.history payloads
// include analytics.* fields per driver
app.post("/hook", (req, res) => {
const payload: WebhookPayload = req.body;
// analytics keyed by driver number
const driver1 = payload.analytics?.["1"];
if (driver1) {
console.log({
tireHealth: driver1.tireHealth, // 0–1 remaining tread
cliffLapPredicted: driver1.cliffLapPredicted, // lap when cliff expected
cliffRisk: driver1.cliffRisk, // "low" | "medium" | "high"
pitStopProbability: driver1.pitStopProbability, // 0–1 probability next lap
safetyCarProbability: driver1.safetyCarProbability,
});
}
res.sendStatus(200);
});
CommonJS
The package ships with both ESM (dist/index.js) and CommonJS (dist/index.cjs) builds. Node.js resolves the correct format automatically based on your project's package.jsontype field.