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:
- We generate a signature using your endpoint's secret key
- We include the signature in the webhook headers
- Your endpoint verifies the signature using the same secret key
- 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:
- Go to your webhook endpoint settings
- Copy the "Signing Secret"
- 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 signaturewebhook-timestamp
: When the webhook was sentwebhook-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:
- Check your signing secret - Make sure it matches what's shown in the dashboard
- Review our code examples - We provide implementation examples in our developer documentation
- Test with our webhook testing tool - Send test webhooks to debug verification issues
- 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();
});
});
Updated 2 days ago