Webhooks: call events delivered to your server
Receive call_started, call_ended, call_analyzed and transfer events from an agent, plus signed account-level events for sessions and balance. This page documents the two delivery paths as they actually behave.
Updated October 6, 2026
There are two webhook paths and they behave differently. Read the table before wiring anything:
| Agent webhook | Account webhook | |
|---|---|---|
| Set on | the agent (webhook_url) | your account (PUT /v1/webhooks) |
| Events | call lifecycle, underscores (call_ended) | sessions and balance, dots (voice_session.ended) |
| Signature | none | X-Call2Me-Signature: sha256=… (HMAC-SHA256) |
| Retries | none — one POST, 10 s timeout | 3 attempts, 1 s / 2 s / 4 s |
| Delivery log | none | webhook_deliveries |
An earlier version of this page described dotted event names, a 24-hour
retry window, a failed-delivery queue, a replay screen and an event_id
field. None of that exists. It was removed on
6 October 2026 after the delivery code was read line by line.
Agent webhooks
Setup
Set the webhook URL on the agent:
curl -X PATCH https://api.call2me.app/v1/agents/agent_abc123 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"webhook_url": "https://your-server.com/webhooks/call2me"}'
Or in the dashboard: Agents → Edit → Webhooks.
Event types
| Event | When | Typical use |
|---|---|---|
call_started | Agent joined the call | Mark the call live in your CRM |
call_ended | Call finished, any reason | Save the recording URL, run your own extraction |
call_analyzed | Post-call analysis is ready | Store the summary and extracted fields |
call_transferred | Agent handed the caller to a human number | Update queue state |
agent_transferred | Call moved to another AI agent | Correlate the two call records |
external_transfer | Call pushed to an external SIP destination | Audit routing |
transfer_failed | A transfer was attempted and the target did not pick up | Alert ops |
transfer_call_ended | The transferred leg ended | Close the ticket |
function_called | The agent invoked one of your custom functions | Log tool usage |
Names use underscores. There is no separate transcript event — the
transcript arrives inside call_ended, and the full turn-by-turn record is
always available afterwards from GET /v1/calls/{call_id}.
Payload shape
Every agent event has the same three-key envelope; everything else is
inside data:
{
"event": "call_ended",
"timestamp": "2026-10-06T14:23:11.481Z",
"data": {
"call_id": "call_xyz789",
"agent_id": "agent_abc123",
"direction": "inbound",
"from_number": "+905551234567",
"to_number": "+908503030053",
"duration_seconds": 134,
"transcript": [ { "role": "agent", "text": "..." }, { "role": "user", "text": "..." } ],
"recording_url": "https://…/calls/call_xyz789.ogg",
"analysis": { "summary": "...", "sentiment": "positive" },
"usage": { "llm_tokens": 1840, "stt_seconds": 75.4, "tts_chars": 570 }
}
}
call_id and agent_id live inside data, not at the top level —
an earlier version of this page put them on the envelope, which is not what
is sent. timestamp is ISO-8601 UTC. Event-specific fields vary; treat
anything beyond event, timestamp and data as absent.
Delivery: one attempt, no signature
The agent path is fire-and-forget. The platform POSTs once with a
10-second timeout; any response below 400 counts as delivered and anything
else — including a timeout — is logged on our side and not retried.
Your endpoint should therefore return 2xx fast and do its work
asynchronously.
There is no signature header on agent webhooks. If you need to verify
origin, use the account-level path below, or validate the call_id
against GET /v1/calls/{call_id} before acting on it.
Deliveries are not guaranteed to be unique; if you retry on your side,
dedupe on data.call_id + event.
Tenant-level webhooks (account events)
The events above are per-agent call events (set via PATCH /v1/agents/{id}).
For account-level events — useful for headless and mobile integrations —
register a single tenant webhook with your sk_ key:
curl -X PUT https://api.call2me.app/v1/webhooks \
-H "Authorization: Bearer sk_live_your_key" \
-d '{"webhook_url": "https://your-app.com/hooks/call2me"}'
# → { "webhook_url": "...", "webhook_secret": "a1b2c3..." } (store the secret)
Two events are delivered here:
voice_session.ended — a headless/AI voice session finished:
{ "event": "voice_session.ended", "timestamp": "2026-07-07T10:00:00Z",
"data": { "room_name": "agent_x_ab12", "agent_id": "agent_x",
"external_user_id": "user_42", "duration_sec": 143.2,
"cost_usd": 0.24, "ended_at": "2026-07-07T10:00:00Z" } }
wallet.low_balance — your balance dropped below the threshold (default $5), at most once per day:
{ "event": "wallet.low_balance", "timestamp": "2026-07-07T10:00:00Z",
"data": { "balance_usd": 3.10, "threshold_usd": 5.0 } }
These are the signed and retried deliveries: X-Call2Me-Signature: sha256=<hex HMAC-SHA256 of the raw body>, three attempts with 1 s, 2 s and
4 s pauses, and every attempt logged to webhook_deliveries with its status
code. The secret is the one returned by PUT /v1/webhooks; store it, it is
not shown again. Agent webhooks (above) do not get this — the two paths
share only the {event, timestamp, data} envelope.
What's next
- Calls — the underlying call object webhooks reference
- Ephemeral tokens — mobile/headless integration tokens
- Post-Call Actions — declarative alternative to webhooks
- Errors & rate limits — what HTTP codes mean
Frequently asked
Q.Which events does an agent webhook send?
call_started, call_ended, call_analyzed, call_transferred, agent_transferred, external_transfer, transfer_failed, transfer_call_ended and function_called. Underscores, not dots. Each carries a call_id for correlation.
Q.Are agent webhooks retried or signed?
No. The agent path is fire-and-forget: one POST, a 10-second timeout, no retry and no signature header. If your endpoint is down the event is gone. Account-level webhooks (below) are the signed, retried path.
Q.Is there a failed-delivery queue or replay?
No. An earlier version of this page described a 24-hour retry window, a failed-delivery queue and a replay button. None of that exists; it was removed on 6 October 2026 after checking the delivery code. Account-level deliveries are logged to webhook_deliveries so you can see what failed, but nothing is replayed automatically.