Guides
Webhooks
Receive every status change of a call, with custom headers, a status filter, retries and a dead-letter list.
A webhook is an HTTP POST the platform sends to your server each time a call changes status. The body is the execution as it is at that moment. This page covers call webhooks; workflows have their own, described in Campaigns and workflows.
In the dashboard: the webhook card in Agent Studio, the Webhooks tab of a call in Call History, and Developers → Jobs for failed deliveries.
Set the URL
The URL is part of the agent config.
curl -X PATCH https://api.sevennadi.si/v2/agent/3c90c3cc-0d44-4b50-8822-8dd25736052a \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{"agent_config": {
"webhook_url": "https://hooks.example.test/calls",
"webhook_headers": {"X-Api-Key": "<your secret>"},
"webhook_statuses": ["completed", "no-answer", "busy", "failed"]
}}'A single call can use a different URL with agent_data.webhook_url, and a batch with its webhook_url form field. Those override the agent's URL for that call.
Headers
webhook_headers are sent with every delivery to the agent's own webhook_url. Use them to authenticate the platform to your server.
- At most 20 headers. Values are strings on one line, up to 2,000 characters.
- Reserved names are refused:
Host,Content-Length,Content-Type,Transfer-Encoding,Connection, and anything starting withx-internal. - Values are encrypted at rest and never returned. Reading the agent shows
{"X-Api-Key": "**********"}. - Headers are sent only to the agent's own URL, never to a per-call or batch URL.
Updating them:
| You send | Result |
|---|---|
No webhook_headers key | Stored headers are kept |
A value of ********** | That header's stored value is kept, so read-then-write is safe |
| An object without a header | That header is removed |
{} or null | All headers are cleared |
If webhook_url moves to a different scheme, host or port, stored headers are dropped unless you send the real values again. Headers without a URL are refused: webhook_headers need a webhook_url.
Status filter
webhook_statuses lists the statuses that produce a webhook. Empty or absent means every status. The allowed values are every call status: scheduled, queued, rescheduled, initiated, ringing, in-progress, call-disconnected, completed, no-answer, busy, failed, canceled, stopped, error, balance-low. GET /user/webhook/allowed-states returns the list.
The filter applies to every delivery for the agent's calls, including per-call and batch URLs.
Payload
The body is the same JSON as GET /executions/{execution_id}: id, agent_id, status, transcript, telephony_data, cost_breakdown, extracted_data, tool_call_logs and the rest. See Post-call data.
{
"id": "b8f0c9a2-3e5d-4c1b-9f7a-2d6e8c4b1a90",
"agent_id": "3c90c3cc-0d44-4b50-8822-8dd25736052a",
"status": "ringing",
"conversation_duration": 0.0,
"total_cost": 0.0,
"telephony_data": {"to_number": "+919876500123", "provider": "twilio", "call_type": "outbound"},
"user_number": "+919876500123",
"created_at": "2026-10-13T09:30:00.000000Z",
"updated_at": "2026-10-13T09:30:03.000000Z"
}Only the completed payload carries the final transcript, cost, summary and extraction. Earlier payloads are snapshots.
Answer with any 2xx status within the timeout (10 seconds by default). Do your own work after answering.
Ordering and retries
Deliveries go through an outbox, so a status is never lost if your server is briefly down.
- Order: for one call, statuses are delivered oldest first. If one delivery fails, later statuses of the same call wait behind it.
- Retries: a delivery that gets a non-2xx answer, times out or cannot connect is retried after a growing wait: 30 seconds, 2 minutes, 10 minutes, 30 minutes, 1 hour, 2, 4, 8, 12, then 24 hours, each with up to 20% random jitter. A server that is down for a day still receives its statuses.
- Limit: ten attempts by default; the nine waits between them add up to about 28 hours. After that the delivery is dead: it stops blocking the statuses behind it and appears in the dead-letter list.
- Duplicates: a status is normally delivered once. If your server processed a request but answered too slowly, the retry will arrive again. Deduplicate on
idplusstatus.
The timeout and attempt limit are operator settings.
Private-network URLs are blocked
Every delivery resolves the URL first. If it points at a private, loopback, link-local or otherwise internal address, it is not sent and not retried; the delivery goes straight to the dead-letter list with an error starting blocked:. The same check applies to the test endpoint, which answers 400 webhook_url is not allowed: ....
Send a test
curl -X POST "https://api.sevennadi.si/v2/agent/3c90c3cc-0d44-4b50-8822-8dd25736052a/webhook:test" \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{"status": "completed"}'{
"webhook_url": "https://hooks.example.test/calls",
"status": "completed",
"headers_sent": ["X-Api-Key"],
"payload": {"id": "...", "status": "completed", "is_test": true},
"result": {"delivered": true, "http_status": 200, "response_body": "ok"}
}The payload is a made-up execution marked "is_test": true. All body fields are optional: send webhook_url and webhook_headers to try values you have not saved yet. A failing endpoint still gives 200, with result.delivered: false, an error_type of http_error or unreachable, and error. With no URL anywhere: 400 The agent has no webhook_url: save one or send webhook_url.
See what was delivered
curl https://api.sevennadi.si/executions/b8f0c9a2-3e5d-4c1b-9f7a-2d6e8c4b1a90/webhooks \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"{"data": [
{"id": 4812, "webhook_url": "https://hooks.example.test/calls", "status": "queued", "attempts": 1,
"response_code": 200, "delivered": true, "delivered_at": "2026-10-13T09:30:00.412000Z", "error": null,
"created_at": "2026-10-13T09:30:00.101000Z"}
]}Resend
POST /executions/{execution_id}/webhook:resend queues the execution's current payload again and answers 202:
{"execution_id": "b8f0c9a2-3e5d-4c1b-9f7a-2d6e8c4b1a90", "webhook_url": "https://hooks.example.test/calls",
"status": "completed", "delivery_id": 4830}The status filter is ignored for a resend. With no URL configured: 409 No webhook is configured for this execution's agent.
Dead-letter list
GET /webhooks/dead lists deliveries that used up their attempts or were blocked, newest first (limit 1 to 200, default 50):
{"total": 1, "deliveries": [
{"id": 4799, "execution_id": "b8f0c9a2-3e5d-4c1b-9f7a-2d6e8c4b1a90", "webhook_url": "https://hooks.example.test/calls",
"status": "completed", "attempts": 5, "response_code": 503, "error": "HTTP 503", "created_at": "2026-10-13T09:31:05.000000Z"}
]}POST /webhooks/dead/{delivery_id}/resend gives one a fresh set of attempts with the same payload and URL, and answers 202.
Testing, resending and the dead-letter list need the admin role or an API key.
Limitations
- Payloads are not signed. Authenticate deliveries with a secret header and serve your endpoint over https.
- Custom headers are not sent to per-call or batch webhook URLs.
- If the operator changes the platform's encryption key without rotating stored secrets, saved headers are lost; webhooks still go out without them.
- Workflow webhook deliveries have no dead-letter view.