Webhook Security
Verify that webhook requests are genuinely from Voxifi to prevent unauthorized access to your systems.
Signature Verification
Every webhook request includes a signature in the headers:
X-Webhook-Signature: sha256=abc123...
X-Webhook-Timestamp: 1705315800Verification Process
- Extract signature and timestamp from headers
- Construct the signed payload:
<timestamp>.<body> - Compute expected HMAC-SHA256 signature using your webhook secret
- Compare with the received signature using a timing-safe comparison
Implementation
Node.js
javascript
const crypto = require('crypto');
function verifySignature(req, webhookSecret) {
const signature = req.headers['x-webhook-signature'];
const timestamp = req.headers['x-webhook-timestamp'];
const body = JSON.stringify(req.body);
// Check timestamp (reject if older than 5 minutes)
const now = Math.floor(Date.now() / 1000);
if (now - parseInt(timestamp) > 300) {
return false;
}
// Compute expected signature
const signedPayload = `${timestamp}.${body}`;
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(signedPayload)
.digest('hex');
// Compare signatures
const receivedSig = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(receivedSig)
);
}
// Usage in Express
app.post('/webhooks/voxifi', (req, res) => {
if (!verifySignature(req, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process verified event...
res.status(200).send('OK');
});Python
python
import hmac
import hashlib
import time
def verify_signature(request, webhook_secret):
signature = request.headers.get('X-Webhook-Signature', '')
timestamp = request.headers.get('X-Webhook-Timestamp', '')
body = request.data.decode('utf-8')
# Check timestamp
if time.time() - int(timestamp) > 300:
return False
# Compute expected signature
signed_payload = f"{timestamp}.{body}"
expected = hmac.new(
webhook_secret.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
# Compare
received = signature.replace('sha256=', '')
return hmac.compare_digest(expected, received)Finding Your Webhook Secret
The signing secret is shown once when you create the webhook endpoint. If you lose it:
- Go to Integrations > Webhooks
- Click on your endpoint
- Click Regenerate Secret to create a new one
Keep Secret Secure
Never expose your webhook secret in client-side code or public repositories.
Timestamp Verification
Always verify the timestamp to prevent replay attacks:
javascript
const MAX_AGE_SECONDS = 300; // 5 minutes
const timestamp = parseInt(req.headers['x-webhook-timestamp']);
const now = Math.floor(Date.now() / 1000);
if (now - timestamp > MAX_AGE_SECONDS) {
// Reject - request is too old
}Best Practices
- Always verify - Never skip signature verification
- Check timestamp - Reject stale requests (older than 5 minutes)
- Use timing-safe comparison - Prevent timing attacks
- Use the raw body - Parse JSON only after verification; middleware that re-serializes the body will break signatures
- Log rejections - Monitor for suspicious activity
Next Steps
- Troubleshooting - Common issues
- Delivery Monitoring - Track delivery