Use webhooks when your application needs to react to terminal call task results without polling.
Phone values in examples are placeholders. Real webhook payloads echo the phone numbers from your call task.
Terminal events
CALL-E sends terminal webhook events:
call.completed
call.failed
call.result_validation_failed
call.result_validation_failed is sent when a completed call task had an internal structured-result validation failure for the task or a recipient. The payload does not expose validation details; invalid or unsupported structured results are returned as null. Failed or canceled terminal call tasks use call.failed.
Each event has an id, type, created_at, and a data object containing the terminal call task fields.
Recipient-level structured results appear inside the recipients array. They do not create separate recipient webhook events.
Attempt transcripts appear as recipients[].attempts[].transcript_turns. Each turn includes offset_seconds, speaker, and text; the array is empty when no transcript is available.
Example payload:
Code
{ "id": "evt_123", "type": "call.completed", "created_at": "2026-06-08T18:30:00Z", "data": { "id": "call_123", "object": "call_task", "status": "completed", "task": "Call each recipient and ask whether they can attend Friday lunch in San Francisco.", "recipients": [ { "id": "rcp_001", "phones": ["<RECIPIENT_1_E164_PHONE>"], "region": "US", "locale": "en-US", "status": "completed", "structured_result": { "can_attend": "yes" }, "summary": "The recipient can attend Friday lunch.", "attempts": [ { "id": "att_001", "phone": "<RECIPIENT_1_E164_PHONE>", "status": "completed", "started_at": "2026-06-08T18:21:00Z", "completed_at": "2026-06-08T18:29:00Z", "summary": null, "transcript_turns": [ { "offset_seconds": 0, "speaker": "bot", "text": "Can you attend Friday lunch in San Francisco?" }, { "offset_seconds": 8, "speaker": "user", "text": "Yes, I can attend." } ], "provider_call_id": "provider_001", "failure_code": null, "failure_message": null } ] }, { "id": "rcp_002", "phones": ["<RECIPIENT_2_E164_PHONE>"], "region": "US", "locale": "en-US", "status": "completed", "structured_result": null, "summary": "The recipient did not provide a usable answer.", "attempts": [ { "id": "att_002", "phone": "<RECIPIENT_2_E164_PHONE>", "status": "completed", "started_at": "2026-06-08T18:22:00Z", "completed_at": "2026-06-08T18:30:00Z", "summary": null, "transcript_turns": [], "provider_call_id": "provider_002", "failure_code": null, "failure_message": null } ] } ], "structured_result": { "attending_count": 1 }, "summary": "One recipient can attend Friday lunch.", "task_completed": true, "completion_confidence": { "score": 0.86, "label": "high" }, "evidence": [ "One recipient confirmed they can attend.", "The second recipient did not provide a usable answer." ], "metadata": { "workflow_run_id": "wf_123" }, "failure_code": null, "failure_message": null, "created_at": "2026-06-08T18:20:00Z", "completed_at": "2026-06-08T18:30:00Z" }}
Signature verification
Verify the signature against the raw request body before parsing JSON. The SDK helpers return the parsed event only after the timestamp and signature are valid.
Both examples listen on POST /calle/webhook, preserve the raw request body for signature verification, and return 200 only after client.webhooks.unwrap succeeds.
Idempotent handling
Webhook delivery is at least once. Store the webhook event id before processing side effects so duplicate deliveries are ignored safely.
TypeScript:
Code
if (await eventStore.has(event.id)) { return new Response("duplicate", { status: 200 });}await eventStore.insert(event.id);await handleCallEvent(event);
Python:
Code
if event_store.has(event["id"]): return {"ok": True, "duplicate": True}event_store.insert(event["id"])handle_call_event(event)