Webhooks
A webhook is an HTTPS endpoint of yours. It receives a POST for each call event that you
subscribe to. To create a webhook, use Developer › Webhooks. You can also use the
API with the webhooks:manage scope.

Event tiers
Events have 2 tiers. The tier decides what you can build on an event.
A best-effort event is a hint about a call in progress. Use it to update a screen. Do not use it to
update a record. The record of a call is call.completed. Reconcile against call.completed or
against GET /cdrs. Never reconcile against the best-effort tier.
Create a subscription
The 201 response has the endpoint and a secret. The secret is the HMAC key for the signature of
the endpoint. The response shows it one time. The dashboard also shows it one time, on its confirmation
screen. Store it now.
The URL must start with https://. Use PATCH /webhooks/{id} to change the name, the URL, the
events, or is_active. While an endpoint is inactive, the system records no deliveries for it. When
you activate it again, the deliveries that are already queued resume. DELETE /webhooks/{id} removes
the endpoint and its queued deliveries. GET /webhooks lists your subscriptions.
The envelope
Every delivery, in either tier, is one JSON body:
A new key in data or a new event type does not change version. Read the keys that you need and
ignore the others.
Verify a delivery
Every request has a signature header:
v1 is HMAC-SHA256("<t>.<raw body>") with the secret of the endpoint as the key. It is hex
encoded. Compute it over the raw bytes that you received. Do not use a re-serialized body. Compare
with a constant-time comparison. Reject a t that is older than your tolerance. The timestamp is in
the signed string, so nobody can replay a captured request later.
Answer with a 2xx status quickly. Do the work after you answer. Any other status, or a timeout,
counts as a failed attempt in the guaranteed tier.
Payloads
The data of every call.* lifecycle event starts with the same 5 keys:
direction is inbound or outbound. In call.completed, it can also be internal. A party type is external, did, or extension. For a call
that the API places, the live events show from as the extension and to as the dialed number.
call_id is the id of the call in call history. call.completed shows the numbers of the sealed record. See Outbound calls.
A best-effort event for a campaign call also has the field campaign_id, after the 5 keys. The event call.completed does not have it. See Campaigns.
Each event then adds its own fields.
call.started
call.answered
An agent party has extension. It also has user_id and name when the extension belongs to a
user. An answer from a queue has the extension only. An external party has destination. An example
is the number that an API-placed call dialed.
call.transferred
call.ended
call.completed
This event is the sealed record. It has the core, then the outcome and the durations that reports use:
outcome_detail is external when a trunk leg to an external system decided the call. A call that a
voice agent handled has outcome: answered and outcome_detail: voice_agent. See
Call history and recordings for all values.
call.recording_ready
This event is not a call lifecycle event, so it does not carry the core. call_id is null when the
system cannot match the recording to a call.
Download the recording before recording_url_expires_at. After that time, the link returns 404.
A replay sends the original link without a change. After the link expires, use
GET /recordings/{recording_id}/audio with your API key. See
Call history and recordings.
call.analysis_completed
This event is not a call lifecycle event, so it does not carry the core. The system sends it when the post-call analysis of a call gives a result. A failed or skipped analysis sends no event.
occurred_at in the envelope is the time when the analysis completed.
Deliveries, retries, and replay
The guaranteed deliveries are in Developer › Events and at
GET /webhooks/deliveries. The newest delivery is first. Each delivery shows the body that the
system sent, the attempt count, the next attempt time, and the last error. You can filter by endpoint
and by status: pending, delivered, or dead.

The system retries a failed attempt with a longer delay each time. After the last attempt, the system marks
the delivery as dead and keeps it. To send it again, click Replay in the drawer, or use
POST /webhooks/deliveries/{id}/replay. The system sends the original body again with the same event
id. If you deduplicate on id, you see 1 event and not 2.
Best-effort events never show in this list. An endpoint that subscribes only to best-effort events has an empty Events page. The page is empty on purpose.