Skip to content
FFastPay Docs

Verify webhook signatures

Anyone who knows your webhook address can send it a request. The signature proves a request came from FastPay and was not changed on the way.

How signing works#

Each request carries a Fastpay-Webhook-Signature header like t=1761644400,v1=3f9a…. t is the time it was signed, in Unix seconds, and v1 is an HMAC-SHA256 of the text <t>.<raw body>, written in hexadecimal and keyed with your endpoint's signing secret.

Verify a request#

  1. Read the raw body, before parsing it as JSON.
  2. Split the header into t and v1.
  3. Compute the HMAC of t + "." + body with your secret.
  4. Compare it with v1 using a constant-time function.
  5. Reject the request if t is more than five minutes from now.
import crypto from "node:crypto";

// `raw` is the request body exactly as received, before any JSON parsing.
export function verify(raw, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.t || !parts.v1 || age > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${raw}`)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Rotate the secret#

You can rotate an endpoint's secret in the dashboard. The new secret is shown once, and the old one stops working at once, so update your server first.