Signature Verification

Why Verify Webhook Signatures?

Webhook signatures let you verify that webhook messages are actually sent by us and not a malicious actor. Without signature verification, anyone could send fake webhook events to your endpoint, potentially causing security issues or data corruption in your system.

Think of webhook signatures like a digital seal on an envelope - they prove the message came from us and hasn't been tampered with during delivery.

How It Works

Every webhook we send includes a cryptographic signature in the request headers. This signature is generated using a secret key that only you and we know. When your endpoint receives a webhook:

  1. We generate a signature using your endpoint's secret key
  2. We include the signature in the webhook headers
  3. Your endpoint verifies the signature using the same secret key
  4. If signatures match, the webhook is authentic and safe to process

What You Need to Do

1. Get Your Signing Secret

Each webhook endpoint has a unique signing secret that you can find in your webhook dashboard:

  1. Go to your webhook endpoint settings
  2. Copy the "Signing Secret"
  3. Store it securely in your application configuration

Important: Keep this secret safe! Anyone with access to it could forge webhook signatures.

2. Verify Signatures in Your Code

Your webhook endpoint must verify the signature of every incoming webhook request before processing it. The verification process uses these headers that we include with every webhook:

  • webhook-signature: The cryptographic signature
  • webhook-timestamp: When the webhook was sent
  • webhook-id: Unique identifier for this webhook delivery

3. Handle Verification Failures

When signature verification fails:

  • Reject the request with a 401 Unauthorized status code
  • Log the failure for security monitoring
  • Do not process the webhook data

Security Benefits

Signature verification protects you from:

  • Malicious attacks: Fake webhooks sent by bad actors
  • Data tampering: Modified webhook payloads during transmission
  • Replay attacks: Old webhooks being resent maliciously
  • Accidental processing: Webhooks from unauthorized sources

Best Practices

Do:

Always verify signatures before processing webhook data
Use HTTPS endpoints for additional transport security ✅ Store signing secrets securely (environment variables, secret managers) ✅ Log verification failures for security monitoring ✅ Rotate secrets periodically if your security policy requires it

Don't:

Never skip signature verification even in development
Don't hardcode secrets in your application code ❌ Don't ignore verification failures - always reject invalid signatures ❌ Don't process webhooks without successful verification

Common Issues

Wrong Secret Key

Make sure you're using the correct signing secret from your webhook endpoint settings. Each endpoint has its own unique secret.

Timestamp Tolerance

Webhooks include timestamps to prevent replay attacks. Allow for reasonable time differences (we recommend 5 minutes) between when the webhook was sent and when you verify it.

Raw Payload Required

The signature is calculated using the exact raw request body. Make sure your verification process uses the original request body, not a modified or re-serialized version.

Getting Help

If you're having trouble with signature verification:

  1. Check your signing secret - Make sure it matches what's shown in the dashboard
  2. Review our code examples - We provide implementation examples in our developer documentation
  3. Test with our webhook testing tool - Send test webhooks to debug verification issues
  4. Check the webhook logs - Failed verifications are logged in your webhook dashboard

Remember: Signature verification is a critical security feature. If you're unsure about your implementation, it's better to ask for help than to skip verification entirely.

describe('WebhookVerification', () => {
  it('should verify valid signatures', () => {
    const payload = '{"test": "data"}';
    const secret = 'whsec_test_secret_key';
    const timestamp = '1614265330';
    
    // Generate test signature
    const signedPayload = `${timestamp}.${payload}`;
    const signature = createHmac('sha256', Buffer.from(secret.substring(6), 'base64'))
      .update(signedPayload, 'utf8')
      .digest('base64');
    
    const headers = {
      'svix-id': 'msg_test',
      'svix-timestamp': timestamp,
      'svix-signature': `v1,${signature}`,
    };
    
    // This should not throw
    expect(() => {
      webhookVerification.verifyWebhook(payload, headers);
    }).not.toThrow();
  });
});