Svix Webhook Signature Verification Failed? Fix Guide

Complete guide to webhooks, debugging, and real-time event monitoring with HookMetry

Svix Webhook Signature Verification Failed? Fix Guide

Problem Introduction

Svix-managed webhooks throw signature mismatch errors on receipt.

Why It Happens

  • Incorrectly formatted verification string — missing the webhook-id.timestamp. prefix
  • Missing message ID (`webhook-id`) or timestamp header
  • Clock skew bypassing 5-minute tolerance limit
  • Using the raw whsec_ string as a key without Base64-decoding it

Step-by-Step Fix

  1. 1Capture raw body as a Buffer using express.raw() — JSON middleware will corrupt it.
  2. 2Extract three headers: webhook-id, webhook-timestamp, webhook-signature.
  3. 3Build the signed content string exactly: `${webhookId}.${webhookTimestamp}.${rawBody}`
  4. 4Strip the "whsec_" prefix from your secret, then Base64-decode the remainder to get raw bytes.
  5. 5HMAC-SHA256 the signed content string using those decoded secret bytes.
  6. 6Base64-encode the result and compare against each signature in webhook-signature (space-separated).

Working Code

Copy-paste verified examples. Use the tab that matches your stack.

const crypto = require('crypto');

app.post('/webhooks/svix', express.raw({ type: 'application/json' }), (req, res) => {
  const webhookId        = req.headers['webhook-id'];
  const webhookTimestamp = req.headers['webhook-timestamp'];
  const webhookSignature = req.headers['webhook-signature'];

  if (!webhookId || !webhookTimestamp || !webhookSignature) {
    return res.status(400).send('Missing Svix headers');
  }

  // Build the exact signed content Svix expects
  const signedContent = `${webhookId}.${webhookTimestamp}.${req.body.toString()}`;

  // Strip "whsec_" prefix, then Base64-decode to get raw key bytes
  const secretBytes = Buffer.from(
    process.env.SVIX_WEBHOOK_SECRET.replace(/^whsec_/, ''),
    'base64'
  );

  const computed = crypto
    .createHmac('sha256', secretBytes)
    .update(signedContent)
    .digest('base64');

  // Header may contain multiple signatures: "v1,<base64> v1a,<base64>"
  const isValid = webhookSignature
    .split(' ')
    .map(s => s.split(',')[1])
    .some(sig => sig === computed);

  if (!isValid) return res.status(401).send('Invalid signature');

  const event = JSON.parse(req.body);
  console.log('Svix event:', event.type);
  res.status(200).send('OK');
});

Common Mistakes

  • Hashing the body directly instead of building the structured Svix string.
  • Failing to decode the secret from Base64 before hashing.
  • Using the full whsec_xxx string as the HMAC key.

Debugging Workflow

Extract headers → decode secret → construct signature string → hash → compare.

Preventive Best Practices

  • Replay test callbacks safely to catch timestamp skew tolerance limits using Hookmetry.

Works with webhooks and other async event systems (including AI callbacks).

Instead of guessing, inspecting the exact payload and headers can help debug faster. Tools like Hookmetry support this workflow.

Try the free webhook tester

Was this page helpful?

Your feedback helps us improve the docs.