Overview
All webhook events emitted by the Voice Agents Backend are cryptographically signed using HMAC-SHA256. The signature is calculated from the payload data, allowing consumers to verify the data’s authenticity and integrity. This approach allows consumers to verify that:- Authenticity: The webhook originated from our service
- Integrity: The payload has not been tampered with during transmission
Keys
Each organization has a unique key pair automatically generated when the organization is created:- Public Key (
publicKey): Formatpk_<32 hex characters> - Secret Key (
privateKey): Formatsk_<64 hex characters>
Signature Headers
Every webhook request includes the following headers along with the payload in the request body:The request body contains the full event payload. The signature is calculated from this payload data. After successful verification, you can trust and use the payload data.
Verification Process
To verify a webhook signature, follow these steps:Step 1: Extract Headers
Extract the following headers from the incoming request:x-signature: The signature to verify againstx-public-key: Used to identify which organization’s secret key to use
Step 2: Get the Secret Key
Using thex-public-key header, retrieve the corresponding secret key for your organization. This should match the secret key stored in your organization settings.
Step 3: Reconstruct the Signature
The signature is calculated from the raw payload string:- Get the raw request body as a UTF-8 string (exactly as received, before any JSON parsing)
- Create an HMAC-SHA256 hash using your secret key
- Update the hash with the raw payload string
- Get the hex digest of the hash
Step 4: Compare Signatures
Compare the computed signature with thex-signature header value. They must match exactly (case-sensitive).
Step 5: Use the Payload Data
After successful signature verification, you can trust and use the payload data from the request body. The signature proves the data’s authenticity and integrity.Code Examples
Node.js
Python
Important Notes
- Raw Body: Always use the raw, unparsed request body for signature verification. Do not use parsed JSON objects, as JSON serialization may differ between systems.
-
Constant-Time Comparison: Use constant-time comparison functions (like
crypto.timingSafeEqualin Node.js orhmac.compare_digestin Python) to prevent timing attacks. - Secret Key Storage: Store secret keys securely (e.g., environment variables, secure key management systems). Never commit them to version control.
- Error Handling: If signature verification fails, return a 401 Unauthorized status and log the event for security monitoring.
- Using Payload Data: After successful signature verification, you can trust and use the payload data from the request body. The signature proves the data’s authenticity and integrity.
Troubleshooting
Signature Mismatch
- Ensure you’re using the raw request body (before JSON parsing)
- Verify you’re using the correct secret key for the public key in the header
- Check that the payload string matches exactly (no extra whitespace, correct encoding)
Missing Headers
- Verify that your webhook endpoint is receiving all required headers:
x-signature,x-public-key - Check your reverse proxy or load balancer configuration to ensure headers are not being stripped