Event types
The typed events a call emits, and what each one carries. This is the internal call vocabulary, used in two places: the call event stream (GET /calls/{call}/events) and the NATS subjects the two planes exchange. Defined as CallEventType in @callagent/contracts and mirrored in App\Enums\CallEventType, with a test that reads the PHP source and fails on drift.
These are not webhook event names
Outbound webhooks use a separate, deliberately versioned vocabulary of 28 names, and the two overlap enough to catch you out:
| Here (call events) | There (webhooks) |
|---|---|
call.created | call.started |
call.connected | call.answered |
tool.started | tool.called |
user.speech.started | speech.started |
Subscribe a webhook endpoint to a name from this page and it is rejected as unknown.
Every event shares the envelope; only data differs, and only that is described below.
Lifecycle
| Event | data |
|---|---|
call.created | direction, from, to, phoneNumberId, assistantId, squadId, campaignId, telephonyProvider, idempotencyKey |
call.ringing | ringingAt, destination |
call.connected | answeredAt, setupMs, sipCallId |
call.ended | endedReason, durationMs, billableSeconds, endedBy (customer · assistant · operator · system · provider) |
call.failed | endedReason, error |
call.state.changed | from, to, reason |
transport.connected | transport, remoteAddress, connectMs, codec, sampleRate |
transport.disconnected | code, reason |
Speech in stt
| Event | data |
|---|---|
user.speech.started | energyDb, duringAssistantSpeech |
user.speech.ended | speechMs, silenceMs |
transcript.partial | text, isFinal: false, confidence, speaker, language, words[] |
transcript.final | as above with isFinal: true |
endpoint.detected | strategy (provider · vad_timeout · punctuation · semantic · hybrid), silenceMs, minSpeechMs, maxWaitMs, confidence, transcript |
words[] carries per-word startMs, endMs, confidence and speaker when the transcriber provides them.
Model llm
| Event | data |
|---|---|
llm.started | provider, model, temperature, maxTokens, messageCount |
llm.first_token | as above plus ttftMs |
llm.completed | plus tokens (prompt, completion, total, cachedPrompt, reasoning), durationMs, finishReason, toolCallCount, fallbackIndex |
llm.failed | error (code, message, statusCode, retryable) |
llm.aborted | abortReason: barge_in · timeout · handoff · call_ended · operator |
finishReason is stop, length, tool_calls, content_filter, aborted or error.
Synthesis tts
| Event | data |
|---|---|
tts.started | voice (provider, voiceId, model), text, chunkIndex |
tts.first_audio | plus ttfaMs |
tts.completed | plus characters, durationMs, audioMs, fallbackIndex |
tts.failed | error |
tts.aborted | abortReason |
Playback playback
| Event | data |
|---|---|
assistant.speech.started | text, messageId |
assistant.speech.interrupted | spokenText, remainingText, spokenMs, detectionMs, stopAudioMs |
assistant.speech.completed | text, audioMs, messageId |
spokenText versus remainingText is the pair that matters: the transcript keeps only what the caller actually heard.
Speech to speech
s2s.started · s2s.first_audio (ttfaMs) · s2s.completed (durationMs, inputAudioMs, outputAudioMs, tokens) · s2s.failed.
Tools tool
| Event | data |
|---|---|
tool.started | toolId, toolCallId, name, toolType, args |
tool.completed | plus result, durationMs, async |
tool.failed | plus error |
tool.rejected | plus rejectionReason, rejectionMessage |
rejectionReason is one of the eleven on error codes.
MCP
mcp.started · mcp.completed · mcp.failed, each carrying serverId, serverName, transport (streamable_http · sse · stdio), toolName, args, and on completion result and durationMs.
Knowledge
| Event | data |
|---|---|
knowledge.queried | knowledgeBaseId, query, provider, matchCount, topScore, durationMs, documentIds |
knowledge.insufficient | knowledgeBaseId, query, threshold, topScore, safetyMode |
knowledge.insufficient is the honest-answer signal: retrieval came back below the threshold and the assistant said so rather than inventing one. See knowledge-only mode.
Routing
| Event | data |
|---|---|
handoff.started | fromAssistantId, toAssistantId, squadId, trigger (tool · intent · variable · dynamic · explicit · silent), contextMode, reason |
handoff.completed | toAssistantId, durationMs, messagesCarried |
transfer.started | mode (blind · warm · queue · sip_uri · assistant · dynamic), destination, destinationType |
transfer.completed | plus legId, durationMs |
transfer.failed | plus error |
Contact centre
operator.queued · operator.ringing · operator.connected · operator.rejected · operator.missed · operator.wrap_up · queue.abandoned · queue.overflowed.
All carry operatorId, operatorName, queueId, queueName, extension, waitMs, ringMs, position and reason where they apply. operator.wrap_up adds dispositionId, disposition, notes and wrapUpMs; queue.abandoned adds abandonedAfterMs; queue.overflowed adds overflowTo and overflowType (queue · ring_group · voicemail · assistant · number).
DTMF, SIP and voicemail
| Event | data |
|---|---|
dtmf.received | digits, method (rfc2833 · rfc4733 · sip_info · inband · provider), buffer |
dtmf.sent | digits, method, interDigitMs |
sip.request.sent | method (INFO · MESSAGE · NOTIFY · REFER · OPTIONS · UPDATE), destination, headers, body |
sip.response.received | statusCode, reason, method, headers |
voicemail.detected | detector (provider_amd · classifier · beep · tool), confidence, beepDetected, policy (hangup · leave_message · continue · custom), detectionMs |
Artifacts
| Event | data |
|---|---|
recording.consent_announced | mode, spokenText, audioUrl, region |
recording.consent_recorded | plus acknowledged |
recording.started | recordingId, channels (mono · stereo · dual), track (mixed · caller · assistant · operator), format |
recording.paused | plus reason |
recording.resumed | as recording.started |
recording.ready | plus url, storagePath, bytes, durationMs |
analysis.completed | summary, sentiment, success, successReason, topics, structuredOutputs, scorecardIds, durationMs |
analysis.completed
The event type and its payload are defined, and the control plane stores the results if they arrive. The post-call analysis pipeline that would produce them is not currently dispatched by the control plane — see Implementation status and Analytics & cost.
Provider health
| Event | data |
|---|---|
provider.fallback | component, from, to, reason (timeout · error · rate_limit · auth · circuit_open · health_check), attempt |
provider.error | component, provider, model, error, circuitOpened |
component is model, transcriber, voice, speech_to_speech, telephony, and for provider.error also storage and observability.
Categorising them
The prefix before the first dot is the category, which is how a consumer can subscribe broadly without enumerating 60 names:
const category = (type: string) => type.split('.')[0];
// 'endpoint.detected' → 'endpoint'
const isError = (type: string) =>
type.includes('failed') || type.includes('error') || type === 'tool.rejected';Both helpers ship as eventCategory() and isErrorEvent() in @callagent/contracts.