Events
| Event | Payload | When |
|---|---|---|
connection_established | — | Joined the session. |
video_play_started | — | The avatar’s video started playing in the element. |
message | { id, role, content, final, typed? } | A caption (a character session), or what the user typed (typed: true). |
session_ended | { reason?, startWait? } | The session is over: you left, the character ended the call, it hit its time limit or silence limit, or the connection dropped. Fires once. Does not fire when streamToVideoElement rejected: that rejection was the answer. startWait says it ended while waiting for a renderer: "cancelled" (the user chose Call later) or "timed_out". |
call_ending | { secondsLeft, reason } | The call is about to end in secondsLeft seconds: its longest is reached (reason: "max_duration"), or your account’s credit runs out ("quota_exceeded"); more reasons may come. Fires once, as the countdown begins. See The call’s last seconds. |
start_waiting | { waitedSeconds, final } | The avatar has not appeared yet and the call is waiting for a renderer: at the first prompt, and again each time the user is asked. final: true — the longest wait is reached and the SDK hangs up (session_ended follows). See Waiting for a renderer. |
tool_call_started | { toolCallId, toolName, toolType, toolSubtype?, arguments, timestamp } | The model called a tool — any kind, see below. |
tool_call_completed | { toolCallId, toolName, toolType, toolSubtype?, result, executionTime, documentsAccessed?, timestamp } | It finished. executionTime in ms. |
tool_call_failed | { toolCallId, toolName, toolType, toolSubtype?, errorMessage, executionTime, timestamp } | It failed. |
Captions. role is "user" (what the user said) or "persona" (what the character says).
The user’s words arrive as they are recognised: the same id repeats with growing content,
and final: true marks the last version — replace by id rather than appending. One id is
one turn of the user’s: pauses inside it don’t start a new line, and final: true comes once the
user has finished and the character takes its turn. The character’s words arrive the same way,
as the avatar speaks them — never ahead of its voice: one id per reply, growing content,
and final: true once the reply has been said (or, if the user cut in, with what was said by then).
The character’s content is plain spoken words: its model is told it is on a voice call, so it
writes no stage directions such as “(smiles)”, no Markdown and no emoji.
const lines = new Map();
client.on("message", (m) => { lines.set(m.id, m); render([...lines.values()]); });Tool calls. The three tool_call_* events fire for every tool the character
calls: toolType is "client" for a client tool your page runs, "server" for
one AvatarGo runs for you, with toolSubtype "webhook", "knowledge" or "system" (end_call and
the like). toolCallId ties a call’s started to its completed or failed. A knowledge search’s
arguments is { query }, written in the language and wording of your documents, which may not be
the user’s; its completed says which documents the passages came from (documentsAccessed). A log of them:
client.on("tool_call_started", (e) => console.log("▸", e.toolName, e.toolSubtype ?? e.toolType, e.arguments));
client.on("tool_call_completed", (e) => console.log("✓", e.toolName, `${e.executionTime} ms`, e.result));
client.on("tool_call_failed", (e) => console.log("✗", e.toolName, e.errorMessage));A webhook set not to wait for its answer completes as soon as the request is sent (result: "Request sent."); how the request then went is not told. What a webhook returns reaches the page in result, so the user’s browser sees it.
Have your endpoint return only what the user may see.
Why a session ended. session_ended’s reason is LiveKit’s DisconnectReason, when the
connection gave one — enough to tell a dropped network from a normal end. For the full story
(end_call, idle_timeout, max_duration, …) your server reads endReason from
GET /v1/sessions/{id}.