Events
Every event the embed can emit. Subscribe via the controller (call.on(event, cb)) or globally (Roxels.on(event, cb)). Events fire identically across modal, compact, and headless modes — the same event stream powers all three.
For narrative usage in headless, see Headless mode.
Lifecycle events
These describe the connection state of the call.
session
Fired once after start(), when the session id is known.
call.on("session", ({ sessionId }) => {
console.log("session is", sessionId);
});Payload:
{
sessionId: string;
}status
Fired on every connection-status change.
call.on("status", ({ status }) => {
// status: prewarm | loading | ready | joining | connected | disconnecting | ended | error
});Payload:
{
status: "prewarm" |
"loading" |
"ready" |
"joining" |
"connected" |
"disconnecting" |
"ended" |
"error";
}connected
Fired once, the first time the call enters connected. After this, voice and chat events start flowing.
call.on("connected", () => console.log("the call is live"));Payload: none.
ending
Fired when the call is gracefully closing (after hangUp() or when the agent decides the conversation is done).
Payload: none.
ended
Fired when the call has finished closing. The iframe is still mounted; cleanup is in progress.
Payload: none.
complete
Fired once the conversation has finished and final results are ready. The most useful "the call is done" hook.
call.on("complete", (results) => {
console.log("Summary:", results.summary);
console.log("Findings:", results.findings);
});Payload:
{
summary: string, // human-readable summary
findings: object, // structured data from all committed goals
duration_seconds: number,
external_id: string,
auto_close: boolean // template setting — should your UI auto-dismiss?
}error
Fired on any call-level error.
call.on("error", ({ error }) => {
if (error === "microphone_blocked") showMicHelp();
});Payload:
{
error: string;
}Common error codes:
| Code | Cause |
|---|---|
microphone_blocked |
Browser denied mic access; check host's Permissions-Policy. |
session_create_failed |
Invalid embed key, domain not allowlisted, or server error. |
connection_failed |
Couldn't reach the LiveKit room. |
session_ended_unexpectedly |
The session ended outside the normal complete path. |
Voice events
voice_state
The agent's voice activity changed.
call.on("voice_state", ({ state }) => {
// state: idle | listening | thinking | speaking
updateOrb(state);
});Payload:
{
state: "idle" | "listening" | "thinking" | "speaking";
}Use this to drive an orb, indicator, or animation in headless UIs.
Chat events
chat
A chat message — either direction.
call.on("chat", ({ id, role, text, timestamp }) => {
appendToTranscript({ role, text, timestamp });
});Payload:
{
id: string,
role: "user" | "assistant",
text: string,
timestamp: string // ISO-8601
}Extraction and goal events
understanding
The agent extracted structured data, or the working understanding of a goal updated.
call.on("understanding", (event) => {
if (event.data) {
console.log("captured:", event.goal_id, event.data);
} else if (event.text) {
console.log("free-form note:", event.text);
}
});Payload (one of):
// Structured extraction (when a goal has a schema)
{
data: object, // matches the goal's schema
goal_id: string,
cumulative: object, // all data captured so far for this goal
source: "extraction" | "tool" | "user",
timestamp: string
}
// Unstructured note (free-form text)
{
text: string,
goal_id: string,
timestamp: string
}goal_transition
A goal committed, or the active goal changed.
call.on("goal_transition", ({ from_goal, to_goal, timestamp }) => {
highlightStep(to_goal);
});Payload:
{
from_goal: string | null, // null if this is the first goal
to_goal: string,
timestamp: string
}Document / form events
These fire when the template uses the live document or form view.
document
The current document state updated.
call.on("document", (state) => renderDocument(state));Payload: the current document state (template-specific shape).
document_clear
A document was cleared.
call.on("document_clear", ({ doc_id }) => clearDocument(doc_id));Payload:
{
doc_id: string;
}Reflecting-state events
These fire whenever the relevant state changes, regardless of who changed it (your code, the user, the agent).
mic_state
Mic enabled/disabled changed.
call.on("mic_state", ({ enabled }) => setMicButtonState(enabled));Payload: { enabled: boolean }
screenshare_state
Screen-share active/inactive changed.
Payload: { active: boolean }
paused / resumed / paused_ended
The pause state changed.
call.on("paused", () => showPausedOverlay());
call.on("resumed", () => hidePausedOverlay());
call.on("paused_ended", () => {
/* call ended while paused */
});Payload: none.
file_uploaded
A file you uploaded finished processing on the agent side.
call.on("file_uploaded", ({ id, filename }) => {
markUploadDone(id, filename);
});Payload: { id: string, filename: string }
command_ack
The iframe acknowledged a command. Useful for debugging command-queue behavior.
Payload: { command: string }
Subscribing patterns
Bind early
You can (and should) bind events before await call.ready. Events that fire during connection (session, status transitions) won't be missed.
const call = Roxels.start({ display: "none", templateKey: "rk-..." });
call.on("status", handleStatus);
call.on("error", handleError);
await call.ready;Bind multiple handlers
Multiple handlers for the same event are fine; they all fire.
call.on("complete", saveToBackend);
call.on("complete", dismissUI);
call.on("complete", trackAnalytics);Unsubscribe
Always unsubscribe in cleanup:
const onChat = (msg) => {
/* ... */
};
call.on("chat", onChat);
// later:
call.off("chat", onChat);call.close() removes all listeners automatically, so this is mostly relevant for long-lived calls where you want to detach a particular handler.
Webhooks vs JS events
Three of these events have webhook equivalents that fire from the Roxels backend to your server:
| JS event | Webhook equivalent | When you'd use webhooks |
|---|---|---|
understanding |
The same data is fired to your webhook (if configured per goal). | When you want backend records, idempotency, retries. |
goal_transition |
Output webhooks fire on goal commit. | When backend systems must act on goal completion. |
complete |
The full result lands at your conversation-completion webhook. | Canonical record of the conversation outcome. |
For backend-critical work, prefer webhooks. They retry, they're idempotent, they survive a closed browser tab. See Webhooks overview.
Read next
- Headless mode — Narrative use of events to build a UI.
- JS API reference — Methods, not events.
- Webhooks — Backend delivery for the same data.