Skip to main content
Anyone who learns your webhook URL can send requests to it. Callers signs its requests so your server can confirm they’re genuine and weren’t modified on the way. Verify the signature before you trust or act on a request. Callers uses two signing schemes, depending on what sent the request: If a request has X-Signature-Type, use it to pick the scheme. If it doesn’t, it’s a simple webhook: use RSA.
Always verify against the raw request body, exactly as received, before parsing it. Parsing JSON and serializing it again can change spacing, key order, or number formatting and break the signature.

Simple webhooks: RSA-SHA256

Callers signs the exact JSON body of each simple webhook with its private key (RSA, PKCS #1 v1.5 padding, SHA-256) and sends the base64 signature in X-Signature. You verify it with the public key.

Callers public key

Save this key in your application, for example as callers-public-key.pem:
public-key.pem

Verify an RSA signature

The Python example uses the cryptography package (pip install cryptography).

Custom webhooks and HTTP actions: HMAC-SHA256

Custom webhooks created with Create Custom Webhook, and HTTP actions your agents run before, during, or after conversations, are signed with a secret that belongs to your account. Each request includes:
  • X-Signature: the lowercase hex HMAC-SHA256 of the request body, keyed with your secret.
  • X-Signature-Type: HMAC.

Get your HMAC secret

1

Open settings

Open Settings → General in the dashboard.
2

Reveal the secret

In Webhook Signature, click Show HMAC Secret and copy the value.
3

Store it securely

Save it as a secret in your server’s environment (for example, CALLERS_HMAC_SECRET). Never put it in client-side code.
Each subaccount has its own secret. Use the secret of the account the agent belongs to.

What is signed

The signature covers the request body after variables are filled in:

Verify an HMAC signature

Compute the HMAC of the raw body with your secret and compare it to X-Signature with a constant-time comparison.

Use RSA instead of HMAC

In the custom webhook or HTTP action editor, turn on Sign with RSA-SHA256. Requests are then signed with the Callers private key, X-Signature-Type is RSA, and you verify them with the public key exactly like simple webhooks. Use this when you don’t want to manage a shared secret.

Text and Email agent webhooks

Signature verification is supported for Voice agent simple webhooks and for custom webhooks and HTTP actions. To authenticate webhooks from Text and Email agents, use a custom webhook (signed with your HMAC secret), or add a secret header in advanced mode and check it on your server.

Unsigned requests

Webhooks in the agent’s Advanced mode (and advanced webhooks created through the API) aren’t signed. Protect them with a secret of your own: add a header such as Authorization: Bearer <random token> in the webhook and reject requests without it. Combine with the Callers IP allowlist.

Best practices

  • Verify before parsing or processing, and reject failures with 401.
  • Use constant-time comparison (crypto.timingSafeEqual, hmac.compare_digest) for HMAC.
  • Keep the HMAC secret out of source control. If it leaks, contact support.
  • Deduplicate on the call id plus webhookEvent, since retries resend the same signed body.
  • Use HTTPS only. Callers requires it for simple webhooks.

Troubleshooting

You’re probably verifying a re-serialized body. In Express, use express.raw() (not express.json()) on the webhook route; in Flask, use request.get_data(); in Django, request.body. Also check you’re using the right scheme: simple webhooks are RSA, custom webhooks default to HMAC.
The signature is computed over the compact JSON of the body Callers built (no extra spaces). If a proxy or framework reformatted the body before your code saw it, verify against compact JSON instead: JSON.stringify(JSON.parse(raw)) in Node.js, or json.dumps(json.loads(raw), separators=(",", ":"), ensure_ascii=False) in Python.
Read X-Signature-Type. HMAC signatures are hex; RSA signatures are base64.
Use the secret from that subaccount’s settings, not the parent account’s.
Email to have a webhook secret set up for your account.

Webhooks overview

Delivery, retries, IP allowlist.

Advanced webhooks

Custom requests and signing options.

Actions

HTTP actions that use the same signatures.

Security and privacy

Account-level security settings.