Verifying signatures
Studio outbound form webhooks optionally sign the exact request body with HMAC-SHA256.
X-InBlack-Signature: sha256=<64 lowercase hex characters>There is no timestamp component in this header. The digest input is the raw body bytes only:
hex(HMAC-SHA256(signing_secret, raw_request_body))Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyInBlackWebhook( rawBody: Buffer, header: string, secret: string,): boolean { if (!header.startsWith("sha256=")) return false;
const supplied = Buffer.from(header.slice("sha256=".length), "hex"); const expected = createHmac("sha256", secret).update(rawBody).digest();
return supplied.length === expected.length && timingSafeEqual(supplied, expected);}Configure your framework to retain the raw body for this route. Re-serializing parsed JSON can change bytes and invalidate the digest.
Verification order
- Require the header when your target has a signing secret.
- Require the
sha256=prefix and valid 32-byte hex digest. - Compute HMAC-SHA256 over the raw body.
- Compare decoded bytes in constant time.
- Parse JSON only after verification succeeds.
- Deduplicate work using
X-InBlack-Delivery-IDor the payload’ssubmissionId.
Because the current signature has no timestamp, replay-window enforcement is not part of the sender contract. Receiver-side deduplication is required for retry and replay resistance.
Provider inbound webhooks use their provider’s scheme. In particular, verify Stripe with Stripe’s library and endpoint secret; do not apply the Studio algorithm merely because both use HMAC-SHA256.
Loading index…