Webhook HMAC Signature Verification

Verify a hex HMAC signature over a webhook body with a timestamp tolerance, using constant-time comparison.

JavascriptSecurity Boundaries

Code

snippet
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyWebhook({ body, signature, timestamp, secret, toleranceMs = 300000, now = Date.now() }) {
  if (typeof body !== 'string' || typeof signature !== 'string' || typeof secret !== 'string') {
    throw new TypeError('body, signature and secret must be strings');
  }
  const ts = Number(timestamp);
  if (!Number.isFinite(ts)) throw new TypeError('timestamp must be numeric');
  if (Math.abs(now - ts) > toleranceMs) return { ok: false, reason: 'timestamp out of tolerance' };
  const expected = createHmac('sha256', secret).update(ts + '.' + body).digest('hex');
  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(signature, 'hex');
  if (a.length !== b.length) return { ok: false, reason: 'signature length mismatch' };
  return timingSafeEqual(a, b) ? { ok: true } : { ok: false, reason: 'signature mismatch' };
}