Skip to content
RateStack
API cookbook

Subscribe to pricing.computed events

Create a webhook subscription, verify deliveries, and idempotently consume events on your side.

RTBy RateStack TeamPublishedReviewed
intermediateTypeScript · Node.js

1. Create the subscription

POST /v1/webhooks
{
  "targetUrl": "https://yourapp.example.com/webhooks/ratestack",
  "eventTypes": ["pricing.computed"]
}

# 201 Created
{
  "id": "wh_C1c8a4ed",
  "targetUrl": "https://yourapp.example.com/webhooks/ratestack",
  "eventTypes": ["pricing.computed"],
  "secret": "whsec_8R2K3J7M..."   ← only shown once. Store immediately.
}

2. Verify on receive

import express from "express";
import crypto from "node:crypto";

const app = express();
const SECRET = process.env.RATESTACK_WEBHOOK_SECRET!;

app.post(
  "/webhooks/ratestack",
  express.text({ type: "*/*" }),
  async (req, res) => {
    const sig = (req.header("x-ratestack-signature") ?? "").replace(/^sha256=/, "");
    const ts = Number(req.header("x-ratestack-timestamp") ?? "0");
    const eventId = req.header("x-ratestack-event-id") ?? "";

    if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > 300) {
      return res.status(400).end();
    }
    const expected = crypto
      .createHmac("sha256", SECRET)
      .update(`${ts}.${req.body}`)
      .digest("hex");
    if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) {
      return res.status(400).end();
    }

    // Idempotency — dedupe by eventId
    if (await alreadyProcessed(eventId)) return res.status(200).end();
    await markProcessed(eventId);

    const body = JSON.parse(req.body) as { type: string; data: unknown };
    await enqueue(body);
    res.status(200).end();
  },
);

Critical points: ack 200 on duplicate (don't 5xx); enqueue rather than process inline; honor the 30-second timeout.

Subscribe to pricing.computed events — API cookbook | RateStack