Event catalogue
An event is a fact that happened, such as “a call was answered”. Each service writes its events in the same step as the change itself, so an event is never published for something that did not happen. There are 35 subjects. The list is also served live by GET /api/integrations/v1/events/catalog, with an example for each.
The envelope
Section titled “The envelope”Every event is wrapped the same way:
{ "id": "…", "tenant_id": "…", "at": "2026-10-06T14:03:11Z", "source": "call-control", "subject": "vd.call.answered", "data": { … } }id is unique and stable, so a receiver can ignore repeats. source is the service that emitted it.
Which events reach webhooks
Section titled “Which events reach webhooks”A webhook connector chooses subjects with wildcards (for example vd.call.*) and optionally one campaign. Every subject below can be chosen. Deliveries are signed, retried and listed under webhooks. Phone numbers in the payload become the last four digits unless the connector asks for full numbers.
The live screens use a second kind of message, called a live topic, such as campaign.runtime, campaign.warning and agent.state. Those are only for the web app and are not delivered to webhooks.
Call (vd.call.*)
Section titled “Call (vd.call.*)”| Subject | When | Main fields |
|---|---|---|
placed |
A call was started | call_id, direction, from_number, to_number, campaign_id, lead_id, attempt_id, journey_id, trunk_id |
ringing |
It is ringing | call_id, journey_id, campaign_id |
answered |
Someone or something answered | call_id, amd_result (human, machine, unknown), handled_by (bot, agent, bot_overflow, none), ring_ms |
failed |
It never connected | call_id, reason (no_answer, busy, rejected, carrier_error, node_failure, cancelled), sip_code |
ended |
An answered call finished | call_id, end_reason (lead_hangup, bot_ended, agent_hangup, transfer_external, node_failure, abandoned, machine, system), talk_ms, answered |
leg_joined, leg_left |
A participant joined or left | call_id, leg_id, kind (lead, bot, agent, supervisor, external), participant, mode |
recording_stored |
A recording was saved | call_id, recording_id, duration_ms, sha256 |
Journey (vd.journey.*)
Section titled “Journey (vd.journey.*)”| Subject | When | Main fields |
|---|---|---|
opened, closed |
A person’s journey starts or ends | journey_id, lead_id, campaign_id; on close status, final_disposition |
stage_started, stage_completed |
A step began or ended | stage_run_id, kind, handler, status |
fact_recorded |
An answer was noted | key, value, plausible, source |
consent_recorded |
Consent was captured on a call | consent_id, kind, status |
disposition_set |
A result was saved | code, category, is_final, call_id |
transfer_requested, transfer_bridged, transfer_failed, transfer_completed |
A hand-over moved on | transfer_id, to_kind, handler, status, billable_seconds, billable, failure_reason |
callback_scheduled |
A callback was booked | callback_id, due_at, reason |
outcome_updated |
A sale or revenue was reported | enrolled, revenue_cents |
Voice (vd.voice.*)
Section titled “Voice (vd.voice.*)”| Subject | When | Main fields |
|---|---|---|
session_started |
A bot conversation began | session_id, call_id, bot_version_id, role |
transcript_line |
A line was spoken | call_id, idx, role (caller, bot, agent, system), text |
reply_timing |
The bot replied | turn, ms |
session_ended |
It ended | end_reason, seconds, cost_usd, turns, reply_ms_p50, reply_ms_p95 |
Transcript text is not scanned for phone numbers.
Dialer, compliance, QA, list and agent
Section titled “Dialer, compliance, QA, list and agent”| Subject | When | Main fields |
|---|---|---|
vd.dialer.attempt_denied |
A dial was refused | attempt_id, campaign_id, lead_id, reason_code (see dial-check reasons) |
vd.dialer.campaign_state_changed |
A campaign ran, paused, drained or stopped | campaign_id, state, reason |
vd.compliance.dnc_added |
A number was added to the do-not-call list | phone_hash_hex, scope, source |
vd.compliance.complaint_logged |
A complaint was logged | complaint_id, journey_id |
vd.qa.scored |
The QA Bot scored a call | review_id, score, passed, flags, needs_review |
vd.list.verified |
A list check finished | list_id, total, ready, rejected |
vd.agent.state_changed |
An agent changed state | user_id, state, pause_code, campaign_id |
Bulk do-not-call imports write one audit line, not one event per number.
Platform (vd.platform.*)
Section titled “Platform (vd.platform.*)”| Subject | When | Main fields |
|---|---|---|
tenant_limits_changed |
An operator changed a plan | limits, changed_by |
ai_minutes_threshold |
AI minutes reached 80% or 100% of the month’s cap | month, used_minutes, cap_minutes, percent |
dialing_pause_changed |
The kill switch was set or lifted | scope (global or tenant), paused, reason, by |
A global pause belongs to no company, so no company’s webhook should receive it.