Skip to Content

Events

EventPayloadWhen
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}.