Skip to main content

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:
  1. Authenticity: The webhook originated from our service
  2. Integrity: The payload has not been tampered with during transmission
After successful signature verification, the consumer can trust and use the payload data included in the request body.

Keys

Each organization has a unique key pair automatically generated when the organization is created:
  • Public Key (publicKey): Format pk_<32 hex characters>
  • Secret Key (privateKey): Format sk_<64 hex characters>
These keys are stored at the organization level and can be found in your organization settings. The secret key is used to sign webhooks, while the public key is included in webhook headers for identification purposes.
Keep your secret key secure and never expose it publicly. Only the public key should be shared or used for verification.

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 against
  • x-public-key: Used to identify which organization’s secret key to use

Step 2: Get the Secret Key

Using the x-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:
  1. Get the raw request body as a UTF-8 string (exactly as received, before any JSON parsing)
  2. Create an HMAC-SHA256 hash using your secret key
  3. Update the hash with the raw payload string
  4. Get the hex digest of the hash

Step 4: Compare Signatures

Compare the computed signature with the x-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

  1. 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.
  2. Constant-Time Comparison: Use constant-time comparison functions (like crypto.timingSafeEqual in Node.js or hmac.compare_digest in Python) to prevent timing attacks.
  3. Secret Key Storage: Store secret keys securely (e.g., environment variables, secure key management systems). Never commit them to version control.
  4. Error Handling: If signature verification fails, return a 401 Unauthorized status and log the event for security monitoring.
  5. 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