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.

json
{
  "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

FieldTypeDescription
idstringUnique event identifier
eventstringEvent name; currently call.ended
createdAtISO 8601 stringTime the event was created
dataobjectCall-specific event data

Call fields

FieldDescription
callIdStable identifier for the call across its lifecycle
agentIdAgent that handled the call
directioninbound or outbound
statusTerminal call status
dispositionBusiness outcome such as qualified, not_qualified, callback, or transferred
durationSecondsConnected conversation duration
from / toCaller and called-party numbers in platform-normalized form
transcriptOptional timestamped transcript turns
recordingUrlOptional authorized recording reference
qualificationOptional qualification result and score

Idempotent handler

typescript
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.