BchainPayBchainPay

Verify a webhook


title: "Verify a webhook" description: "Validate the BchainPay-Signature header before trusting a webhook payload." section: "Webhooks" order: 2 updated: "2026-04-18" sourcePath: "content/docs/webhooks/verify.mdx"

Every BchainPay webhook is signed with HMAC-SHA256 using your per-merchant secret. Verify the signature on every request before acting on the payload.

The signature header

BchainPay-Signature: t=1714397200,v1=8b0a...e3
  • t — Unix timestamp when we signed the request.
  • v1 — Hex-encoded HMAC-SHA256 of ${t}.${rawBody} using your secret.

Verification recipe

import crypto from "node:crypto";

export function verify(rawBody: string, header: string, secret: string, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;

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

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

Replay protection

Reject any request whose t is more than 5 minutes from the current time. We retry failed deliveries with exponential backoff for 30 days, but each retry carries a fresh signature.

What if I miss a webhook?

Pull the latest state from GET /v1/payment-intents/:id. The API is the source of truth; webhooks are an optimization.

Last updated Edit on GitHub