Skip to content
VoxaDocs
Navigation
Open console →

Webhooks

Webhook events

This page lists every event Voxa sends during a call, when it is sent, and the fields it carries in data.

Each event is also saved on the call’s timeline (GET /api/v1/calls/{id}/events), with the same type, message, level, latency_ms and data.

Order of events

A typical outbound call:

call.queued → call.dequeued → call.placed → call.started → welcome → tts.first_audio
→ user.turn → llm.first_token → tts.first_audio → agent.reply
→ user.turn → llm.first_token → tool.call → tool.result → tts.first_audio → agent.reply
→ ... → tool.call (end_call) → call.ended → call.completed

Inbound and browser calls start at call.started. A call that never connects ends with call.ended and has no call.completed.

All events

EventWhenExtra data fields
call.queuedAn outbound call joined the queue.position, by
call.dequeuedA slot was free and the call is about to be dialled. latency_ms is how long it waited.
call.placedPlivo accepted the dial request.
call.startedThe call connected and the agent is live.channel, from, to, stt, llm, tts, started_by, values
welcomeThe agent starts saying its welcome message (the text is in message).
tts.first_audioThe agent’s first audio of a reply played. latency_ms is from the caller finishing to this moment.welcome: true for the welcome message
user.turnThe caller finished a turn (message is what they said).after_goodbye: true if said after the agent decided to hang up
llm.first_tokenThe LLM started answering. latency_ms is from the caller finishing.model_ms: time since the LLM request was sent
agent.replyThe agent finished a reply (message is the full text).
interruptedThe caller talked over the agent, which stopped (message is what the caller heard).
tool.callThe agent called a tool.name, args, url (name only for end_call)
tool.resultThe tool answered or failed. latency_ms is the request time. level is warning unless the status is 2xx.name, status, result
user.online_checkThe agent asked whether the caller is still there.
hangup.promptThe hang-up prompt decided the conversation is over.
errorSomething went wrong. level is warning or error.Sometimes provider
call.endedThe call ended, for any reason.see below
call.completedThe final call record is saved, after post-call analytics.call
pingA test from POST /api/v1/webhooks/{id}/test.

call.started

{
  "type": "call.started",
  "data": {
    "message": "outbound call with Appointment reminder",
    "level": "info",
    "latency_ms": null,
    "channel": "phone",
    "from": "+918035001234",
    "to": "+919812345678",
    "stt": "cartesia/ink-2",
    "llm": "gemini-3.5-flash-lite",
    "tts": "sonic-3.6/Riya",
    "started_by": "",
    "values": ["customer_name", "slot"]
  }
}

values lists the user_data keys the call has (not their values). started_by is the person’s name for browser calls.

tool.result

{
  "type": "tool.result",
  "data": {
    "message": "check_availability -> HTTP 200",
    "level": "info",
    "latency_ms": 184,
    "name": "check_availability",
    "status": 200,
    "result": {"status": 200, "result": {"free_slots": ["10:30", "12:00"]}}
  }
}

result is what the LLM received (see Tools), cut to about 3000 characters. status is 0 when your endpoint did not answer.

call.ended

When a call that connected ends:

{
  "type": "call.ended",
  "data": {
    "message": "ended by agent: agent ended the call",
    "level": "info",
    "latency_ms": null,
    "duration_seconds": 14.2,
    "avg_first_audio_ms": 1050,
    "usage": {"stt_seconds": 14.0, "tts_characters": 121, "llm_input_tokens": 1840, "llm_output_tokens": 41, "llm_calls": 1}
  }
}

level is error when the call ended because of an error.

When a call never connected, message says why and there is no usage:

CasemessagelevelExtra
Plivo reported no answer, busy, failed or cancelednot connected: no-answerwarningplivo: Plivo’s status
Canceled while queuedcanceled while queuedinfo
Expired, out of credits, agent deleted, dial refusedthe hangup_reasonwarning (error for credits and refusals)

call.completed

Sent once per connected call, after post-call analytics, with the full call object plus transcript_text:

{
  "id": "evt_1f2e3d4c5b6a79880a1b2c3d4e5f6a7b",
  "type": "call.completed",
  "created_at": "2026-10-01T09:14:21.733000+00:00",
  "workspace_id": "6df74233a1b04c2e9f8d7c6b5a4e3d2c",
  "call_id": "00b1d622a8f04f4f8f3a3c2d9e5b7a11",
  "agent_id": "1b81a241c0e44f5c9d1f0e2a7c3b9d10",
  "data": {
    "message": "call completed",
    "level": "info",
    "latency_ms": null,
    "call": {
      "id": "00b1d622a8f04f4f8f3a3c2d9e5b7a11",
      "agent_name": "Appointment reminder",
      "direction": "outbound",
      "to_number": "+919812345678",
      "status": "completed",
      "user_data": {"customer_name": "Rohan", "slot": "11 am"},
      "transcript": [{"role": "assistant", "text": "Hello Rohan, ...", "at": "2026-10-01T09:14:02.410000Z", "interrupted": false}],
      "transcript_text": "assistant: Hello Rohan, ...\nuser: Yes, I'll be there at eleven.\nassistant: Great, we'll see you tomorrow at 11 am. Goodbye!",
      "duration_seconds": 14.2,
      "cost_paise": 95,
      "hangup_by": "agent",
      "hangup_reason": "agent ended the call",
      "summary": "City Clinic called Rohan to confirm tomorrow's 11 am appointment. ...",
      "extracted": {"confirmed": true, "new_day": null},
      "...": "..."
    }
  }
}

Times in webhooks are in UTC. This is the event to use for syncing results to your CRM: it has the outcome, the transcript, the analytics and the cost in one place.

ping

{
  "id": "evt_ping",
  "type": "ping",
  "created_at": "2026-10-01T09:30:00.000000+00:00",
  "workspace_id": "6df74233a1b04c2e9f8d7c6b5a4e3d2c",
  "call_id": "",
  "agent_id": "",
  "data": {"message": "Test event from Voxa"}
}
Esc