Skip to main content
Every event envelope has this top-level shape:
Below, each event’s data block is documented.

Call lifecycle

call.started

Fires when the dialer originates a call (pre-answer).

call.answered

Fires on CHANNEL_ANSWER — the callee picked up.

call.ended

Fires on CHANNEL_HANGUP, for every call, once the call and lead rows have been finalised — so a GET /v1/calls/{id} issued the moment you receive the event already returns the same durations and disposition.
call.ended carries no phone number and no lead name. Join on lead_id (or salesforce_lead_id) against your own record if you need the contact details — this keeps the event safe to log and safe to deliver for tenants under PII-strict handling.
recording.available is usually false at hangup — the upload to storage has not finished yet. call.recording_ready follows 30–120 s later with the same recording.url, which is stable and safe to persist: it re-signs storage on every fetch instead of handing you a URL that expires.

call.disposition_set

Fires when a call’s behavioural outcome is recorded — Interested, Site Visit, Do Not Call. call.ended carries the mechanical outcome (answered, busy, no answer); this carries what the call was actually worth, and it is usually the event a CRM wants. It fires twice in two different situations, and previous_disposition tells them apart: Re-saving the same value is not a transition and publishes nothing, so a double-clicked Save or a retried AI verb will not produce a duplicate.
call.disposition_set carries no phone number, no lead name, and no agent notes. Join on lead_id (or salesforce_lead_id) against your own record if you need contact details. Notes are excluded deliberately: free text an agent typed is the one field here where a number can arrive without any field being named phone.
Whether this event reaches you at all is governed by event routing as well as your subscription’s own event filter.

call.recording_ready

Fires when the recording is uploaded and available for playback — typically 30–120 s after call.ended.
recording.url is a non-expiring, HMAC-tokenised public link — it never rotates or expires and needs no API key, so it’s the field to persist in external systems: a Salesforce Call_Detail__c.Recording_File__c, a CRM attachment, a webhook subscriber’s own database. The legacy top-level recording_url is the tenant-scoped, API-key-gated audio proxy (also stable, but requires your API credentials on every fetch — use it from server-side code that already holds them). signed_url is a direct GCS link that expires after 1 hour; use it only for an immediate fetch, never for storage — persist recording.url or recording_url instead.

call.transcript_ready

Fires when Gemini transcription completes (typically +30–60s after recording upload).

Lead lifecycle

lead.completed

Fires when a lead reaches a terminal state (completed, failed, dnd, or no_answer after exhausted retries).

Campaign lifecycle

campaign.started

campaign.paused

Fires on BOTH manual and auto-pause. Distinguish via data.source:

campaign.resumed

Symmetric to paused. source is "manual" in all current paths (auto-resume from safety-sweep doesn’t exist — humans re-enable).

campaign.completed

reason is "stopped" when an operator or an API call stopped it, and "leads_finished" when it stopped by itself because all its leads were finished. previous_status is running or paused.
This is not a final event. A stopped campaign can be restarted, which fires campaign.reopened. If you close a record on campaign.completed, subscribe to campaign.reopened too.

campaign.reopened

Fires when a stopped campaign is restarted (completed → running) — the usual reason being leads imported after the stop. A campaign that stopped by itself (reason: "leads_finished") restarts by itself when new leads arrive, and fires this too. Emitted alongside campaign.started, never instead of it, so subscribing to this is only necessary if you treated campaign.completed as final. completed_at is the timestamp the campaign carried before the restart cleared it, or null if it never had one.

Agent lifecycle

agent.logged_in

agent.logged_out

v2 events (coming)

These will land in v1.1 / v2: