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.