Webhooks & EventsSchema
call.ended Event Schema
Process completed call outcomes, transcripts, recordings, and qualification context idempotently.
10 min readUpdated 25 Sept 20264 guides in this section
The call.ended event is emitted when a call reaches a terminal outcome. The payload can include transcript and recording references, but consumers should rely on the stable event and call identifiers for idempotency.
{
"id": "evt_01JZQ7M2C8",
"event": "call.ended",
"createdAt": "2026-09-25T10:30:00.000Z",
"data": {
"callId": "call_01JZQ6Y7AF",
"agentId": "agent_01JZQ4M2PX",
"direction": "outbound",
"status": "completed",
"disposition": "qualified",
"durationSeconds": 184,
"from": "+91XXXXXXXXXX",
"to": "+91XXXXXXXXXX",
"transcript": [
{ "speaker": "agent", "text": "How can I help?", "startMs": 0, "endMs": 1200 },
{ "speaker": "caller", "text": "I am looking for a three-bedroom home.", "startMs": 1250, "endMs": 3900 }
],
"recordingUrl": "https://example.invalid/recordings/call_01JZQ6Y7AF",
"qualification": {
"qualified": true,
"score": 82
}
}
}Top-level fields
Call fields
Idempotent handler
export async function handleCallEnded(event: CallEndedEvent) {
const exists = await deliveries.has(event.id);
if (exists) return;
await database.transaction(async (tx) => {
await tx.deliveries.create({ id: event.id });
await tx.calls.upsert({
key: event.data.callId,
status: event.data.status,
disposition: event.data.disposition,
durationSeconds: event.data.durationSeconds
});
});
}Need implementation help?
Search the help center or talk with the OP26X telephony team.