Overview
Both <tavus-widget> and <tavus-embed> support two-way communication with the host page:
- Element → page: the element dispatches
CustomEvents (with bubbles: true and composed: true) for conversation lifecycle, tool calls, and protocol messages.
- Page → element: the element exposes an imperative API at
element.tavus for starting, ending, and messaging the conversation.
CDN consumers use plain DOM APIs - no extra bundle needed. npm consumers can use the typed TavusIntegration helper exported from @tavus/widget and @tavus/embed.
Events
Listen on the element itself, or on document (events bubble out of the shadow DOM):
const el = document.querySelector("tavus-embed");
el.addEventListener("tavus:conversation-started", (e) => {
console.log("started", e.detail.conversationId);
});
el.addEventListener("tavus:tool-call", (e) => {
console.log("tool call", e.detail.name, e.detail.arguments);
});
| Event | detail | Fires when |
|---|
tavus:conversation-started | { conversationId } | The visitor joins and the conversation connects. |
tavus:conversation-ended | { conversationId } | The conversation ends. |
tavus:state-change | { state } | The conversation state changes (connecting, connected, ended, …). |
tavus:mode-change | { mode } | The experience switches mode (for example between screens or modalities). |
tavus:error | { code, message } | The conversation hits an error. |
tavus:tool-call | { name, arguments, seq?, turn_idx? } | The PAL’s LLM invokes a tool. arguments is a JSON string. |
tavus:protocol-message | varies | Firehose: fires for every observable protocol event (utterances, perception, speaking state, …). Switch on detail.event_type. |
tavus:protocol-message carries the raw Interaction Events protocol - detail.event_type values such as conversation.utterance, conversation.tool_call, conversation.started_speaking, and conversation.stopped_speaking match the schemas documented there. For speaking state, check detail.properties.role ("user", "pal", or legacy "replica"). See Started/Stopped Speaking Event.
Imperative API
The element exposes its conversation controls on element.tavus:
const el = document.querySelector("tavus-widget");
await el.tavus.start(); // start the conversation
el.tavus.sendChat("Hello!"); // send a chat message as the visitor
el.tavus.sendMessage({ // send a typed protocol interaction
event_type: "conversation.echo",
properties: { text: "Read this aloud" },
});
await el.tavus.end(); // end the conversation
| Method | Description |
|---|
start() | Starts the conversation, as if the visitor pressed the start button. Returns a promise. |
end() | Ends the active conversation. Returns a promise. |
sendChat(text) | Sends a chat message into the conversation as the visitor. |
sendMessage(interaction) | Sends a typed protocol interaction. See Interactions. |
element.tavus is attached once the element has mounted and its configuration has loaded. Wait for the element to render (or for a tavus:state-change event) before calling into it.
Interactions
sendMessage accepts the same interaction shapes as the Interactions Protocol:
event_type | properties | Effect |
|---|
conversation.echo | { text } | The PAL speaks the text verbatim. See Echo. |
conversation.respond | { text } | The text is sent to the LLM, which responds. See Respond. |
conversation.interrupt | - | Interrupts the PAL mid-speech. See Interrupt. |
conversation.append_llm_context | { context } | Appends to the conversation’s LLM context. See Append Context. |
conversation.overwrite_llm_context | { context } | Replaces the conversation’s LLM context. See Overwrite Context. |
TavusIntegration helper (npm)
npm consumers get a typed wrapper around the element from the same package as the registration side effect:
import "@tavus/embed";
import { TavusIntegration, type TavusInteraction } from "@tavus/embed";
// Pass the tag name to target: "tavus-embed" (default) or "tavus-widget".
const integration = new TavusIntegration("tavus-embed");
integration.on("tavus:conversation-started", (e) => {
console.log("started", e.detail.conversationId);
});
integration.on("tavus:protocol-message", (e) => {
console.log(e.detail);
});
const interaction: TavusInteraction = {
event_type: "conversation.respond",
properties: { text: "Tell me about pricing" },
};
integration.sendMessage(interaction);
// Plain strings are wrapped as conversation.respond for convenience.
integration.sendMessage("Tell me about pricing");
| Method | Description |
|---|
on(event, handler) | Subscribes to a typed deployment event. |
off(event, handler) | Removes a previously added handler. |
sendMessage(interaction | string) | Sends a typed interaction. A plain string is wrapped as conversation.respond. |
The helper looks up the element by tag name in the DOM, so construct it (or make the first call) after the element exists on the page.
TavusIntegration is bundled into the ESM builds of @tavus/widget and @tavus/embed - no extra install. The CDN (IIFE) build stays a pure side-effect drop-in; CDN pages use the DOM APIs above instead.