Webhooks let Callr notify your server when something happens on your account: a call starts or ends, an SMS is received, a recording is ready, your prepaid credit runs low... Subscribe with Create webhook: one webhook per event type and endpoint.
Delivery
Callr sends an HTTP POST request to your endpoint, with a JSON body (Content-Type: application/json, User-Agent: Callr API). HTTP redirects are followed.
| Header | Content |
|---|---|
X-Callr-EventId | Unique event ID, same as id in the body |
X-Callr-HmacSignature | Base64 HMAC signature of the raw body, only when the webhook has options.hmac (see below) |
Every event has the same envelope:
| Field | Description |
|---|---|
id | Unique event ID. Use it to deduplicate events. |
at | Date of the event (RFC 3339) |
sid | SID of the webhook |
type | Event type, e.g. v2.call.inbound.ended |
try | Delivery attempt, starting at 0 |
payload | The event data, described on each event page |
{
"id": "a0331744032581221927Lm4RtYvB8nQe",
"at": "2025-04-07T13:29:41.221Z",
"sid": "p7w2dz5c",
"type": "v2.call.inbound.ended",
"try": 0,
"payload": { ... }
}Responding
Return any 2xx status code. Anything else is a failure: another status code, a connection error, or no response within 10 seconds (5 seconds to connect).
Respond first, process laterStore the event and reply
200right away, then process it asynchronously.
Retries
A failed delivery is retried, up to 10 attempts in total (try from 0 to 9). After attempt n, Callr waits n × 10 seconds (10 s, 20 s... 90 s): about 7.5 minutes overall. Every attempt carries the same id.
Events can arrive more than once, and out of order: deduplicate them on id, and order them with at.
Verifying the signature
When you create a webhook with options.hmac (secret and algorithm: sha256 or sha512), every request carries an X-Callr-HmacSignature header: the base64-encoded HMAC of the request body, computed with your secret.
To verify it, compute the HMAC over the raw body (the bytes you received, before parsing the JSON), encode it in base64, and compare it to the header in constant time:
import crypto from 'node:crypto';
// rawBody: the request body as received (Buffer), before JSON parsing
// signature: req.headers['x-callr-hmacsignature']
function isValidCallrSignature(rawBody, signature, secret, algorithm = 'sha256') {
const expected = Buffer.from(
crypto.createHmac(algorithm, secret).update(rawBody).digest('base64')
);
const received = Buffer.from(signature || '');
return expected.length === received.length && crypto.timingSafeEqual(expected, received);
}See the recipe Create a webhook and verify its signature for a complete walkthrough.
Automatic deactivation
Every failed attempt counts. After 500 failed attempts, the account contact and technical emails receive a warning. After 1,000 failed attempts, the webhook is disabled (status disabled) and an email is sent.
The counter goes back to zero when you re-enable the webhook (Update webhook with status: enabled), or after 60 days without any failure.
The statuses disabled-admin and disabled-system mean that the webhook was disabled by Callr: please contact our support team.
Logs
Every delivery attempt, with the HTTP status code returned by your endpoint and its duration, is available with Search webhook logs.
Events
| Event | Triggered when |
|---|---|
v2.audio-file.updated | An audio file has an updated status |
v2.billing.credit.topup | Your prepaid account is credited |
v2.billing.credit.warning | Your prepaid balance reaches its warning threshold |
v2.billing.credit.shutdown | Your prepaid balance reaches its shutdown threshold |
v2.call.inbound.started | An inbound call starts |
v2.call.inbound.ended | An inbound call ends |
v2.call.outbound.started | An outbound call starts |
v2.call.outbound.ended | An outbound call ends |
v2.number.assigned | A phone number is assigned to your account |
v2.number.unassigned | A phone number is unassigned from your account |
v2.recording.updated | A recording has an updated status |
v2.sms.inbound.received | An inbound SMS is received |
v2.sms.outbound.updated | An outbound SMS has an updated status |
v2.task.updated | A task has an updated status |
