SDK

Node.js SDK

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.

Node.js ≥ 18TypeScript 5.xESM + CJSZero runtime deps
GitHub repository →npm package →

Installation

bash
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

FieldTypeDescription
clientIdstringYour RaceHooks OAuth client ID.
clientSecretstringYour RaceHooks OAuth client secret.
baseUrlstring?Override the API base URL. Defaults to https://api.racehooks.io.
timeoutnumber?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.

rh.webhooks

typescript
// List all webhooks
const { webhooks } = await rh.webhooks.list();

// Get a specific webhook
const { webhook } = await rh.webhooks.get("wh_7a3b9c2d");

// Create with filters
const { webhook: wh, webhookSecret } = await rh.webhooks.create({
  feedId:     "timing.data",
  webhookUrl: "https://your-app.com/hook",
  filters: {
    constructors: ["ferrari"],
    positions:    { min: 1, max: 5 },
  },
});

// Pause
await rh.webhooks.update("wh_7a3b9c2d", { active: false });

// Delete
await rh.webhooks.delete("wh_7a3b9c2d");

// Send a test delivery
const result = await rh.webhooks.test("wh_7a3b9c2d");

// Rotate signing secret
const { webhookSecret: newSecret } = await rh.webhooks.rotateSecret("wh_7a3b9c2d");

// Delivery logs
const { logs } = await rh.webhooks.logs("wh_7a3b9c2d", { limit: 50 });

rh.usage

typescript
// 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.json type field.

javascript
// CommonJS
const { RaceHooks, verifySignature } = require("racehooks");

const rh = new RaceHooks({
  clientId:     process.env.RACEHOOKS_CLIENT_ID,
  clientSecret: process.env.RACEHOOKS_CLIENT_SECRET,
});

Error handling

The SDK throws typed errors you can catch and handle:

typescript
import { RaceHooks, RaceHooksAuthError, RaceHooksAPIError } from "racehooks";

try {
  await rh.webhooks.create({ feedId: "timing.data", webhookUrl: "..." });
} catch (err) {
  if (err instanceof RaceHooksAuthError) {
    // OAuth token request failed — check credentials
    console.error("Auth failed:", err.message);
  } else if (err instanceof RaceHooksAPIError) {
    // API returned an error response
    console.error(`API error ${err.statusCode}:`, err.message);
  }
}
← Race EventsMCP Server →