Skip to content
VaakyoDocs
Navigation
Open console →

Webhooks

Webhook events

This page lists every event Vaakyo 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.

Lifecycle events and timeline events

By default (the agent’s webhook URL, and endpoints subscribed to "*") you get the call’s lifecycle: call.queued → call.placed (dialling) → call.started (in progress) → call.ended → call.completed (the final record, after analytics). An endpoint can also list, by name in events, any of call.voicemail, call.rescheduled, call.transferred, call.transfer_status, tool.call, error and guardrail.violation (GET /api/v1/webhooks/events returns the list). The other events below are on the call’s timeline (GET /api/v1/calls/{id}/events) but are not sent to endpoints.

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 (data.provider is plivo).
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
kb.searchThe agent searched its knowledge bases. latency_ms is the whole search; level is warning if it failed.query, hits, embed_ms, search_ms, passages, top (source, meaning score and keyword match of each passage found), error if it failed
kb.inlineThe agent’s knowledge bases are small enough and were put whole into its prompt.characters
user.online_checkThe agent asked whether the caller is still there.
hangup.promptThe hang-up prompt decided the conversation is over.
fallback.usedA fallback voice or transcriber took over for the rest of the call. level is warning.component (tts or stt), reason, from, to
capacity.fallbackThe voice’s or transcriber’s provider was at its concurrency limit, so a fallback voice spoke that reply, or a fallback transcriber took the call. Once per call for each component and provider. level is warning.component (tts or stt), provider (the full one), from, to
capacity.exceededThe provider was at its concurrency limit and no fallback had room, so the call went ahead on it anyway. level is warning.component, provider, from
call.dtmfThe caller pressed phone keys (keypad input on); message is what the agent received, e.g. [keypad: 1 2 3 #].digits (e.g. "123#")
call.voicemailAn answering machine picked up (voicemail detection on): the agent leaves its voicemail message, or hangs up.answered_by (the carrier’s verdict, e.g. machine_end_beep), message (true when a message is left)
errorSomething went wrong. level is warning or error.Sometimes provider
call.rescheduledA call back was booked (auto reschedule); message gives the local time. level is warning when the after-call check found a time it could not book.call_id (the booked call), at (UTC), source (tool or post_call), note
call.transferredThe agent handed the caller to a person (transfer to a human); the agent’s part ends next.name, number, reason
guardrail.violationAfter the call, the platform guardrail check found that the agent broke a rule (Guardrails). One event per incident. level is error for a violation, warning for a warning.incident_id, rule_id, rule_name, severity, quote, confidence, action (notice, frozen or warning), strike
call.transfer_statusThe transfer’s outcome: the person answered, or no one did. level is warning unless answered.status (answered, no-answer, busy, failed or canceled), name, number, plivo (the carrier’s dial status, when not answered)
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
The carrier reported no answer, busy, failed or cancelednot connected: no-answerwarningplivo: the carrier’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 Vaakyo"}
}

call.voicemail

The carrier’s answering-machine detection heard a machine. The call then ends with hangup_reason voicemail and the call’s answered_by is machine.

{
  "type": "call.voicemail",
  "data": {
    "message": "answering machine: leaving the voicemail message",
    "level": "info",
    "latency_ms": null,
    "answered_by": "machine_end_beep",
    "message": true
  }
}

Plivo reports only that a machine answered (answered_by is machine), not what kind.

fallback.used

The agent’s voice or transcriber failed and a backup took over. from and to name the provider, model, and the voice or language.

{
  "type": "fallback.used",
  "data": {
    "message": "voice switched to cartesia/sonic-3 (Meera): voice not found",
    "level": "warning",
    "latency_ms": null,
    "component": "tts",
    "reason": "voice not found",
    "from": {"provider": "cartesia", "model": "sonic-3.6", "voice_id": "a0e99841-438c-4a64-b679-ae501e7d6091", "voice_name": "Riya", "language": "hi"},
    "to": {"provider": "cartesia", "model": "sonic-3", "voice_id": "f9836c6e-a0bd-460e-9d3c-f7299fa60f94", "voice_name": "Meera", "language": "hi"}
  }
}

A backup that fails to connect at the start of the call has a reason starting with could not connect.

capacity.fallback

Speech providers limit how many streams and replies run at once. When the voice’s provider is full, the next fallback voice from another provider with room speaks that one reply (later replies go back to the main voice once it has room); when the transcriber’s provider is full as the call starts, the first fallback transcriber with room takes the call. Add fallback voices and transcribers from other providers so a busy provider never slows a call.

{
  "type": "capacity.fallback",
  "data": {
    "message": "cartesia voice capacity full: this reply uses elevenlabs/eleven_flash_v2_5 (Anika)",
    "level": "warning",
    "latency_ms": null,
    "component": "tts",
    "provider": "cartesia",
    "from": {"provider": "cartesia", "model": "sonic-3.6", "voice_id": "a0e99841-438c-4a64-b679-ae501e7d6091", "voice_name": "Riya", "language": "hi"},
    "to": {"provider": "elevenlabs", "model": "eleven_flash_v2_5", "voice_id": "zT03pEAEi0VHKciJODfn", "voice_name": "Anika", "language": "hi"}
  }
}

With no fallback that has room, a reply waits up to 1.5 seconds and is then spoken by the main voice anyway (capacity.exceeded): the call is never left silent.

Esc