Svix

Updated May 02, 2026

Overview

Svix is a webhook delivery platform used by many SaaS products. Its signing scheme uses three headers and includes a message ID in the signed string.

Add the signing secret to Splithook

The signing secret is available from the Svix dashboard (or API) — it starts with whsec_.

  1. Settings → Signing secrets → New.
  2. Provider: Svix.
  3. Paste the whsec_ secret.
  4. In your destination, set Signing mode to Re-sign, select this secret.

Verify signatures in your handler

Use the official Svix SDK:

// Node.js
import { Webhook } from 'svix';

app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  const wh = new Webhook(process.env.SVIX_WEBHOOK_SECRET);
  let payload;
  try {
    payload = wh.verify(req.body, {
      'svix-id': req.headers['svix-id'] as string,
      'svix-timestamp': req.headers['svix-timestamp'] as string,
      'svix-signature': req.headers['svix-signature'] as string,
    });
  } catch (err) {
    return res.status(400).send('Invalid signature');
  }
  // handle payload...
  res.sendStatus(200);
});
# Python
from svix.webhooks import Webhook
wh = Webhook(os.environ['SVIX_WEBHOOK_SECRET'])
payload = wh.verify(request.data, {
    'svix-id': request.headers.get('svix-id'),
    'svix-timestamp': request.headers.get('svix-timestamp'),
    'svix-signature': request.headers.get('svix-signature'),
})

How Splithook re-signs Svix events

Svix uses three headers:

svix-id: msg_2TqxxxxxxxxxxxI
svix-timestamp: 1714900000
svix-signature: v1,base64encodedhmac==

The signed string is: {svix-id}.{svix-timestamp}.{body}.

On replay, Splithook:

  1. Keeps svix-id unchanged (message ID is stable across replays).
  2. Replaces svix-timestamp with now().
  3. Recomputes HMAC-SHA256(secret, "{svix-id}.{new_timestamp}.{body}").
  4. Base64-encodes and prefixes with v1,.

Svix verifiers check the timestamp within a 5-minute tolerance — the refresh ensures replays pass.

Filtering

Svix-based platforms typically put the event type in the JSON body. Use event_type (if set via a header) or body.type:

body.type == 'invoice.created'
body.type matches '^invoice\.'