Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.vocobase.com/llms.txt

Use this file to discover all available pages before exploring further.

Webhook Payloads

This page documents the structure of webhook payloads sent by Vocobase when events occur.

Event: session.completed

Sent when a call session ends — whether it completed normally, failed, or was disconnected.

Headers

Every webhook request includes these headers:
HeaderDescription
Content-Typeapplication/json
X-Webhook-SignatureHMAC-SHA256 signature: sha256=<hex>
X-Webhook-TimestampISO 8601 timestamp when the event was sent

Payload structure

{
  "event": "session.completed",
  "timestamp": "2026-03-15T14:32:08.000Z",
  "data": {
    "session_id": "s1234567-abcd-1234-abcd-123456789012",
    "agent_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "agent_name": "Sales Assistant",
    "duration_seconds": 127,
    "credits_used": 2.12,
    "processing_complete": true,
    "pipeline_completed_at": "2026-03-15T14:32:08.000Z",
    "transcript": [
      {
        "role": "bot",
        "content": "Hello! Thanks for taking my call. How can I help you today?",
        "timestamp": "2026-03-15T14:30:01.000Z"
      },
      {
        "role": "user",
        "content": "Hi, I'm interested in your enterprise plan.",
        "timestamp": "2026-03-15T14:30:04.500Z"
      },
      {
        "role": "bot",
        "content": "Great choice! Our enterprise plan includes unlimited agents, priority support, and custom voice training.",
        "timestamp": "2026-03-15T14:30:05.200Z"
      }
    ],
    "recording_url": "https://downloads.example.com/recordings/rec_abc123.wav?signature=...",
    "variables": {
      "callee_name": "Sajal",
      "mobile_number": "+919876543210"
    },
    "extraction": {
      "status": "success",
      "values": {
        "interested": true,
        "callback_at": "2026-05-01T15:00:00Z",
        "outcome": "callback_requested"
      },
      "model": "vocobase-managed",
      "extractedAt": "2026-03-15T14:32:09.840Z",
      "latencyMs": 1840,
      "attempts": 1
    },
    "call": {
      "call_id": "c1234567-abcd-1234-abcd-123456789012",
      "from_number": "+14155551234",
      "to_number": "+919876543210",
      "direction": "outbound",
      "status": "completed",
      "disposition": "completed",
      "answered_by": "human",
      "transferred": false,
      "transferred_at": null,
      "transfer_target": null
    }
  }
}

Field reference

FieldTypeDescription
eventstringAlways "session.completed"
timestampstring (ISO 8601)When the webhook was sent
dataobjectEvent data (see below)

Data object fields

FieldTypeDescription
session_idstring (UUID)Unique session identifier
agent_idstring (UUID)The agent that handled the call
agent_namestringAgent display name at time of call
duration_secondsintegerCall duration in seconds
credits_usednumberCredits deducted for this call
processing_completebooleantrue when post-call processing has finished. Always true on the webhook payload itself. Use it when polling GET /calls/{id} to tell final results from in-progress processing.
pipeline_completed_atstring (ISO 8601) or nullTimestamp when post-call processing finished. Non-null on the webhook. null when polling GET /calls/{id} and processing has not completed yet, or on older sessions that pre-date this field.
transcriptarrayOrdered list of conversation turns (see below)
recording_urlstring or nullPresigned URL for the call recording. null if recording was not enabled. URL is valid for 7 days. If it expires, mint a fresh one with GET /calls/{call_id}/recording-url.
variablesobjectPre-call variable values that were substituted into the agent’s prompt and greeting. Empty object {} when no variables were supplied. Echoed for partner-side audit.
extractionobject or absentPost-call extraction result, present only when the agent has a Custom Analysis config defined. Status is success, failed, or skipped (no transcript). Values are typed per the agent’s config; missing keys are emitted as null. See Post-call Extraction below.
callobject or absentCall metadata. Present only for telephony sessions started via POST /calls/start. Omitted entirely for browser WebRTC sessions started via POST /sessions/webrtc (no associated phone call).
call.call_idstring (UUID)Unique call identifier
call.from_numberstringCaller phone number (E.164)
call.to_numberstringDestination phone number (E.164)
call.directionstring"outbound"
call.statusstringCall lifecycle/carrier status: completed, failed, no_answer, busy, canceled. Voicemail is exposed through call.disposition, not call.status.
call.dispositionstring or nullFinal call outcome: completed, voicemail, user_hangup_no_speech, no_answer, busy, canceled, blocked, or failed.
call.answered_bystring or nullBest-known answer classification: human, machine, unknown, or not_answered. machine indicates an answering machine or voicemail.
call.transferredbooleantrue if the bot handed the call off to a human via the cold-transfer flow. See Transferred calls below.
call.transferred_atstring (ISO 8601) or nullWhen the bot left the room and the carrier began bridging the caller to the human. null for non-transferred calls.
call.transfer_targetstring or nullE.164 destination the caller was bridged to. null for non-transferred calls.

Transcript entry format

Each entry in the transcript array has:
FieldTypeDescription
rolestring"user" or "bot"
contentstringWhat was said
timestampstring (ISO 8601)When this turn occurred
recording_url is a presigned URL valid for 7 days from the moment the webhook fires. Download and persist the recording on your side if you need long-term retention. If the URL expires before you can download it, call GET /calls/{call_id}/recording-url to mint a fresh one with another 7-day window.

Call status values

call.status is the lifecycle/carrier state. It intentionally does not include voicemail; use call.disposition for the final outcome.
StatusDescription
completedCall connected and ended normally
failedCall could not be placed (provider error)
no_answerRecipient did not answer
busyRecipient’s line was busy
canceledCall was canceled before connecting

Call disposition values

DispositionDescription
completedHuman-answered call completed normally
voicemailCarrier AMD or bot-side detection classified the answer as voicemail
user_hangup_no_speechThe callee answered, then hung up quickly before speaking
no_answerRecipient did not answer
busyRecipient’s line was busy
canceledCall was canceled before connecting
blockedCarrier/provider rejected or blocked the call
failedCall failed or ended with an unmapped error outcome

Transferred calls

When an agent transfers a live call to a human:
  1. The bot speaks the configured announcement (transfer_message).
  2. Vocobase transfers the caller to transfer_target.
  3. The session.completed webhook includes transfer metadata.
The session.completed webhook fires once the bot leg ends, not when the caller eventually hangs up with the human. This means:
  • duration_seconds reflects only the bot leg (caller-on-with-bot time).
  • recording_url, when present, is the bot-leg recording. The human side is not recorded.
  • call.transferred is true; call.transferred_at is the redirect moment; call.transfer_target is the destination E.164.
Use call.transferred as the cheap discriminator if your downstream automation needs to distinguish a transferred call from a normal hangup.
{
  "call": {
    "call_id": "c1234567-abcd-1234-abcd-123456789012",
    "from_number": "+14155551234",
    "to_number": "+919876543210",
    "direction": "outbound",
    "status": "completed",
    "disposition": "completed",
    "answered_by": "human",
    "transferred": true,
    "transferred_at": "2026-05-06T14:32:11.000Z",
    "transfer_target": "+14155559999"
  }
}

Polling GET /calls/{id} vs webhook

Both the webhook and GET /api/v2/calls/{id} return the same data shape, so a partner that can’t accept inbound webhooks (or wants to reconcile dropped deliveries) can poll the GET endpoint instead. A call can appear completed before all transcript, recording, and extraction fields are ready. The processing_complete boolean tells you when the response is final:
status                processing_complete    meaning
─────────────────────────────────────────────────────────────────────────────
active                false                  call still in progress
completed             false                  call ended; post-call processing still running
completed             true                   final — transcript / recording / extraction are stable
failed                true                   terminal failure (no_answer, busy, reject); no transcript
async function fetchUntilReady(callId) {
  while (true) {
    const { data } = await vocobase.get(`/calls/${callId}`);
    if (data.session?.processing_complete === true) return data;
    if (data.status === 'failed' || data.status === 'no_answer' || data.status === 'busy') return data;
    await sleep(30_000);
  }
}
In practice the session.completed webhook will reach you before polling kicks in — polling is the fallback for partners that can’t accept inbound webhooks, or for reconciliation after a delivery failure.
For sessions created before this field shipped, pipeline_completed_at will be null permanently. Treat null + status: completed on an older row as “final” — those rows pre-date the signal.

Post-call extraction

When the agent has a Custom Analysis config defined, the platform runs an LLM extraction over the transcript after every completed call and ships the result inline in the same session.completed webhook — no second webhook to handle. Configure extraction via the dashboard’s “Variables & Analytics” tab on the agent editor or via PUT /api/v2/agent/:id with extraction_config.

Extraction object structure

{
  "status": "success",
  "values": {
    "interested": true,
    "callback_at": "2026-05-01T15:00:00Z",
    "outcome": "callback_requested"
  },
      "model": "vocobase-managed",
  "extractedAt": "2026-03-15T14:32:09.840Z",
  "latencyMs": 1840,
  "attempts": 1
}
FieldTypeDescription
statusstringsuccess, failed, or skipped
valuesobject or absentExtracted fields, typed per the agent’s extraction_config.keys. Missing fields are emitted as null. Absent entirely when status != 'success'.
errorstring or absentFailure reason — present only when status === 'failed'
modelstringOpaque extraction model identifier. Do not branch business logic on this value.
extractedAtstring (ISO 8601)When extraction ran
latencyMsintegerLLM latency in milliseconds
attemptsintegerNumber of attempts (1 = success on first try; 2-3 = retried)
Extraction failures never block webhook delivery. The unified payload always ships with extraction.status indicating outcome — partners that need extraction values can branch on status === 'success', partners that just need transcript + recording can ignore the field.

Replay extraction against a past session

If you edit extraction_config and want to backfill an existing session, call POST /api/v2/calls/{call_id}/extract with { "dry_run": false }. Optional dry_run: true returns the result without persisting. The replay uses the agent’s current config (not the config at original-call time).

Pre-call variables

The variables field on every webhook echoes the per-call values that were substituted into the agent’s prompt and greeting. Configure variable names via the dashboard “Variables & Analytics” tab or PUT /api/v2/agent/:id with variables: ["callee_name", "mobile_number"]. Supply per-call values via POST /api/v2/calls/start body field variables: { callee_name: "Sajal" }. Missing values render as empty strings — the platform never errors on missing variables.

Next steps

Webhook Setup

Configure webhook endpoints and verify signatures.

Error Handling

Understand retry behavior and failure handling.