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: "raceevent",
webhookUrl: "https://yourserver.com/f1-hook",
});
// SAVE webhookSecret immediately — only shown once
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);
// Today's delivery count
const today = await rh.usage.current();
console.log(today.deliveryCount, today.dailyRemaining);
// 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: "2025-british-r1",
speed: 10, // 10× real-time
feeds: ["raceevent", "timingdata"], // 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);
Analytics tier payloads
On the Analytics tier, timingdata, cardata, and tyrestintseries 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 Analytics tier, timingdata / cardata / tyrestintseries 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.