Verifying the Signature
Verifying the signature is required. The signature is HMAC-SHA256(secret, "<t>.<rawBody>") in hex, where <t> is the t= value from the X-Qwik-Signature header and <rawBody> is the exact bytes Qwik sent. Verify against the raw body before parsing JSON.
The secret is the one returned once when you created the webhook (or rotated its secret) - see Webhook Management.
Node.js example
Node.js · verify webhook
const crypto = require('crypto'); function verifyQwikWebhook(rawBody, signatureHeader, secret) { const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.split('='))); const timestamp = parts.t; const expected = parts.v1; // 1. Reject events older than 5 minutes (replay protection) if (Math.abs(Math.floor(Date.now() / 1000) - parseInt(timestamp, 10)) > 300) return false; // 2. Recompute and constant-time compare const computed = crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex'); return crypto.timingSafeEqual(Buffer.from(computed, 'hex'), Buffer.from(expected, 'hex')); }
Reject anything that doesn't verify (return 401).
Compute the HMAC over the raw request body bytes, before any JSON parsing or re-serialization. Re-stringifying the parsed body can change whitespace/ordering and break verification.