Hash the raw request body, not parsed JSON. Reject deliveries older than a few minutes so a captured request cannot be replayed.
Check X-RevenueCat-Webhook-Signaturejavascript
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifySignature(rawBody, header, secret, toleranceSeconds = 300) {
const m = /(?:^|,)\s*t=(\d+)\s*,\s*v1=([0-9a-f]{64})\s*(?:,|$)/.exec(header ?? '');
if (!m) return false;
const t = Number(m[1]);
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest();
const received = Buffer.from(m[2], 'hex');
return received.length === expected.length && timingSafeEqual(received, expected);
}