Signed Webhooks
To ensure the security and authenticity of webhook deliveries, every webhook request is signed with a cryptographic signature that you can verify on your side.
Signature Format
The signature is included in the x-webhook-signature header with the following format:
t={timestamp},v1={signature}
Where:
timestamp(t) → Unix timestamp (in seconds) when the signature was generated.signature(v1) → HMAC SHA-256 hash of the payload combined with the timestamp.
Signature Verification
To validate the authenticity of a received webhook, follow these steps:
Extract the
timestamp(t) andsignature(v1) from thex-webhook-signatureheader.Validate the
timestampto ensure it is not older than 5 minutes (protects against replay attacks).Recalculate the expected signature by combining the extracted timestamp with the raw request payload, and hashing it using HMAC SHA-256 with your webhook secret.
Compare the recalculated signature with the one provided in the header using a constant-time comparison function (to prevent timing attacks).
If the timestamp has expired or the signatures do not match, reject the request (e.g., respond with HTTP 401 Unauthorized).
Example — TypeScript (Node.js)
import * as crypto from 'crypto';
function verifyWebhookSignature(payload: string, signatureHeader: string, secret: string) {
const [tPart, v1Part] = signatureHeader.split(',');
const timestamp = tPart.split('=')[1];
const signature = v1Part.split('=')[1];
const fiveMinutesAgo = Math.floor(Date.now() / 1000) - 300;
if (parseInt(timestamp, 10) < fiveMinutesAgo) {
throw new UnauthorizedException('Signature timestamp expired');
}
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload, 'utf8')
.digest('hex');
const valid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature),
);
if (!valid) {
throw new UnauthorizedException('Invalid signature');
}
return valid;
}Example usage

