Skip to content

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: 1705315800

Verification Process

  1. Extract signature and timestamp from headers
  2. Construct the signed payload: <timestamp>.<body>
  3. Compute expected HMAC-SHA256 signature using your webhook secret
  4. 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:

  1. Go to Integrations > Webhooks
  2. Click on your endpoint
  3. 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

  1. Always verify - Never skip signature verification
  2. Check timestamp - Reject stale requests (older than 5 minutes)
  3. Use timing-safe comparison - Prevent timing attacks
  4. Use the raw body - Parse JSON only after verification; middleware that re-serializes the body will break signatures
  5. Log rejections - Monitor for suspicious activity

Next Steps

Voxifi - AI-Powered Voice Assistant Platform