Verify webhook signatures

Five lines of code and your endpoint only trusts payloads that provably came from your workspace.

For admins

Updated

Anyone who discovers your endpoint URL can POST to it. The signature header is how your receiver knows a payload actually came from HoldFast, and it takes five lines to check.

What arrives

Every webhook request carries an X-HoldFast-Signature header: an HMAC-SHA256 of the raw request body, keyed with your connection's signing secret (shown when you create the webhook connection), hex-encoded.

Verify it

Compute the same HMAC over the raw body and compare:

import crypto from 'crypto';

function verifyHoldFast(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}

Two rules keep this working: hash the raw bytes as received (parse the JSON only after verifying, since any re-serialization changes the hash), and compare with a timing-safe equality rather than ===.

When verification fails

Reject the request with a 401 and move on; do not process the body. A burst of failures usually means a rotated secret on one side only: re-copy the signing secret from the connection in Settings → Integrations into your receiver's configuration.

Questions

Why does my computed signature not match?

Nine times out of ten: the body was parsed and re-serialized before hashing. Compute the HMAC over the raw request bytes exactly as received, before any JSON parsing.

Ready to try it?

Host video, collect frame-accurate review, and see who's watching. Free to start.

Start free