Skip to main content
Every event envelope has this top-level shape:
{
  "event_id": "1f2e...-uuid",
  "event_type": "call.ended",
  "timestamp": 1744934400,
  "data": { ... }
}
Below, each event’s data block is documented.

Call lifecycle

call.started

Fires when the dialer originates a call (pre-answer).
{
  "call_id": "c-123",
  "campaign_id": "camp-567",
  "lead_id": "l-890",
  "to_number": "9876543210",
  "dial_time": "2026-04-18T14:37:02.419Z"
}

call.answered

Fires on CHANNEL_ANSWER — the callee picked up.
{
  "call_id": "c-123",
  "campaign_id": "camp-567",
  "answer_time": "2026-04-18T14:37:08.102Z",
  "ring_duration_s": 5
}

call.ended

Fires on CHANNEL_HANGUP. The most common event customers subscribe to.
{
  "call_id": "c-123",
  "campaign_id": "camp-567",
  "lead_id": "l-890",
  "end_time": "2026-04-18T14:39:02.102Z",
  "hangup_cause": "NORMAL_CLEARING",
  "talk_duration_s": 114,
  "disposition": "interested",
  "recording_url": null
}
recording_url is null on call.ended; wait for call.recording_ready once the GCS upload completes (typically +2–5s after hangup).

call.recording_ready

Fires when the recording is uploaded and a signed URL is available.
{
  "call_id": "c-123",
  "recording_url": "https://storage.googleapis.com/yotel-recordings/...",
  "duration_s": 114
}
The signed URL expires 7 days after issue. If you archive recordings, pull them down promptly.

call.transcript_ready

Fires when Gemini transcription completes (typically +30–60s after recording upload).
{
  "call_id": "c-123",
  "transcript_url": "https://storage.googleapis.com/yotel-transcripts/...",
  "language": "ta-IN",
  "duration_s": 114
}

Lead lifecycle

lead.completed

Fires when a lead reaches a terminal state (completed, failed, dnd, or no_answer after exhausted retries).
{
  "lead_id": "l-890",
  "campaign_id": "camp-567",
  "status": "completed",
  "attempts": 2
}

Campaign lifecycle

campaign.started

{ "campaign_id": "camp-567" }

campaign.paused

Fires on BOTH manual and auto-pause. Distinguish via data.source:
sourceMeaning
"manual"Operator paused via dashboard or /predictive/pause endpoint. user_id + reason included.
"abandon_rate_exceeded"D11 in-memory controller tripped the 60s sustained-breach threshold.
"safety_sweep"D13 Celery sweep’s DB-based fallback tripped the threshold. Runner was probably crashed.
{
  "campaign_id": "camp-567",
  "source": "manual",
  "reason": "Carrier complaint — investigate",
  "user_id": "user-42"
}

campaign.resumed

Symmetric to paused. source is "manual" in all current paths (auto-resume from safety-sweep doesn’t exist — humans re-enable).
{
  "campaign_id": "camp-567",
  "source": "manual",
  "reason": "Abandon rate back under 1%",
  "user_id": "user-42"
}

campaign.completed

{
  "campaign_id": "camp-567",
  "stats": {
    "total_leads": 1250,
    "connected": 847,
    "abandoned": 31,
    "connect_rate": 0.678
  }
}

Agent lifecycle

agent.logged_in

{
  "agent_id": "ag-7",
  "extension": "1042",
  "user_id": "user-42"
}

agent.logged_out

{ "agent_id": "ag-7" }

v2 events (coming)

These will land in v1.1 / v2:
EventPurpose
campaign.createdCRM sync — register the campaign on your side
lead.addedPost-create hook for just-added leads
agent.state_changedFine-grained agent state stream (noisy — may be opt-in)
supervisor.session_startedBarge / whisper / monitor start