Verifying signatures
Every signed webhook carries:
content-type: application/json
x-newinstance-timestamp: <unix ms>
x-newinstance-signature: sha256=<hex HMAC-SHA256(secret, raw request body)>
Verify against the raw body bytes, before any JSON parsing or re-serialisation, and compare in constant time. Reject payloads whose embedded timestamp is older than a few minutes to blunt replays.
Node
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(secret, rawBody, signatureHeader) {
const expected = `sha256=${createHmac('sha256', secret).update(rawBody).digest('hex')}`;
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader ?? '');
return a.length === b.length && timingSafeEqual(a, b);
}PHP
function verify(string $secret, string $rawBody, ?string $signatureHeader): bool {
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
return hash_equals($expected, $signatureHeader ?? '');
}If no secret is configured for the destination, the signature headers are omitted entirely — treat unsigned deliveries as untrusted hints, never as facts.