Skip to content

Incoming webhooks

Trusting a payload an external service POSTs at Cratebase (Stripe, GitHub, another Cratebase instance’s own outgoing webhook) is the opposite direction from _webhooks above, and reduces to one primitive: $security.hs256(data, secret), the constant-time HMAC-SHA256-as-hex computation the embedded JS runtime already exposes to every pb_hooks/*.pb.js file — no server-side Rust module or dedicated route needed, since there is nothing left for the host to implement that $security.hs256 doesn’t already cover.

A pb_hooks file registers its own inbound-webhook endpoint with the existing routerAdd mechanism. Stripe’s Stripe-Signature header (t=<unix-seconds>,v1=<hex>[,v1=<hex>...], multiple v1 values while a signing secret is being rotated — a match against any one is valid) is the worked example because its scheme is the de facto reference design most other HMAC-webhook providers copy:

routerAdd("POST", "/webhooks/stripe", (e) => {
const sig = e.request.header.get("Stripe-Signature") || "";
const parts = Object.fromEntries(
sig.split(",").map((p) => p.split("=").map((s) => s.trim()))
);
const secret = $os.getenv("STRIPE_WEBHOOK_SECRET");
const body = e.request.body; // raw bytes read by the runtime
const signedContent = parts.t + "." + body;
const expected = $security.hs256(signedContent, secret);
const age = Math.floor(Date.now() / 1000) - Number(parts.t);
if (expected !== parts.v1 || Math.abs(age) > 300) {
throw new BadRequestError("invalid webhook signature");
}
const event = JSON.parse(body);
console.log("verified Stripe webhook:", event.type);
});

The timestamp check exists because a signature alone only proves “the secret holder produced this digest at some point,” not when — without it, a captured request stays replayable forever even though its signature is technically valid; 300 seconds matches Stripe’s own official libraries’ default tolerance.

See the incoming-webhooks-stripe example for the full walkthrough, including trying it against a locally running server with curl and a hand-computed signature.