> ## Documentation Index
> Fetch the complete documentation index at: https://cntillion.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Event listeners

> React to messages, tool calls, dictation and runtime options from the website widget.

The [website embed](https://cntillion.com/docs/channels/website.md) exposes `window.centillion.addEventListener`
and `window.centillion.removeEventListener`. Use them to update your page, track
conversation activity or connect your own analytics and CRM code.

## Available events

Every callback receives `{ type, data }`.

| Event | `data` | When it fires |
| - | - | - |
| `user-message` | `{ content: string }` | The composer accepts a message to send, including a suggested question. This does not confirm server delivery. |
| `assistant-message` | `{ content: string }` | The complete AI answer finishes appearing in the chat. One event per answer. |
| `tool-call` | `{ args, id: string, name: string, type: "tool-call" }` | An action starts. `args` contains its JSON inputs. |
| `tool-result` | `{ name: string, result, toolCallId: string, type: "tool-result" }` | An action returns its JSON result. `toolCallId` matches the call's `id`. |
| `dictation-transcript` | `{ transcript: string }` | Dictation finishes, immediately before its text is inserted into the composer or sent in dictation-only mode. Empty or failed transcripts emit nothing. |
| `optionsChanged` | `{ options: object }` | The widget renders its initial runtime overrides, then whenever the overrides change. |

Tool events cover your custom actions: server actions and registered website
functions. Built-in actions such as live chat, procedures and suggested messages
emit no tool events. Tool events arrive while the chat request is running. Inspect the result to determine whether an
action succeeded. A call or result event alone does not confirm a successful
purchase, booking or form submission.

Greeting messages, conversation resets, errors and human handoff notices do not
emit `assistant-message`. Restored history does not replay message events.
Attachments travel with the chat request; `user-message` contains only the
message text, which can be empty for a message containing only files.

Dictation and sending are separate steps. Visitors can edit the transcript
before sending, so the later `user-message` can contain different text. Dictation
uses the browser's speech recognition service and requires microphone access.
The dictation control requires **Voice to text (Dictation)** in Display settings.
Browsers without speech recognition show an explanatory message. In dictation-only
mode, a completed transcript is sent automatically. Voice
calls have a separate control and do not emit `dictation-transcript`.

## Add and remove listeners

Run this after the embed script has loaded:

```javascript theme={null}
function onUserMessage(event) {
  console.log("User message:", event.data.content);
}

window.centillion.addEventListener("user-message", onUserMessage);

// Use the same function reference when removing it.
window.centillion.removeEventListener("user-message", onUserMessage);
```

You can register several callbacks for an event. Registering the same callback
twice adds it once. A failed callback does not stop the other callbacks or the
chat. `window.Centillion` exposes the same listener methods, so existing identity
integrations can keep their capitalized name.

Listeners remain registered while the host page stays loaded, including when
a published chat reloads after a reset. The new conversation emits its own
message events; the reset and its greeting do not emit `assistant-message`.

Listeners receive events from every Centillion widget loaded by the embed script
on the page. For separate event streams per agent, use the iframe integration
below and check each iframe's `contentWindow`.

### Register before the script loads

Queue calls before your existing embed script tag:

```html theme={null}
<script>
  window.centillion = window.centillion || function (...args) {
    (window.centillion.q = window.centillion.q || []).push(args);
  };

  window.centillion("addEventListener", "assistant-message", (event) => {
    console.log("AI answer:", event.data.content);
  });
  window.centillion("setOptions", { displayName: "Acme support" });
</script>
<!-- Paste your Centillion embed script here. -->
```

The loader replays queued calls in order. It loads the chat iframe when a visitor
opens or approaches the launcher. The first `optionsChanged` event therefore
waits for the iframe to load, even though the JavaScript API is already available.
Use a current snippet from **Channels → Install → Website widget** if your
browser has cached an older loader.

### React cleanup

```jsx theme={null}
useEffect(() => {
  const onAnswer = (event) => setLastAnswer(event.data.content);
  window.centillion.addEventListener("assistant-message", onAnswer);
  return () => {
    window.centillion.removeEventListener("assistant-message", onAnswer);
  };
}, []);
```

Load the embed before mounting this component, or use the queued call form for
both registration and removal. Remove listeners on unmount or route changes.

## Runtime options

`setOptions` merges valid fields into the current overrides. `resetOptions()`
clears all overrides. To clear selected fields, pass an object whose values are
`true`. Overrides last for this page load and never change dashboard settings.

```javascript theme={null}
window.centillion.addEventListener("optionsChanged", (event) => {
  // The full set of overrides currently rendered, without dashboard defaults.
  console.log(event.data.options);
});
window.centillion.setOptions({
  displayName: "Acme support",
  messagePlaceholder: "Ask about your order",
  suggestedMessages: ["Track my order", "Contact support"],
});
window.centillion.resetOptions({ displayName: true });
window.centillion.resetOptions();
```

| Option | Accepted value |
| - | - |
| `displayName` | Header title, up to 100 characters. Also updates the launcher's accessible label. |
| `bubbleText` | Launcher text, up to 40 characters. An empty string hides the text. |
| `footer` | Footer text, up to 1,000 characters. |
| `messagePlaceholder` | Composer placeholder, up to 100 characters. |
| `dismissibleNotice` | Notice above the composer, up to 500 characters. |
| `initialMessages` | Array of non-empty strings, up to 1,000 characters total. Applies before the conversation starts, or after the next reset. |
| `suggestedMessages` | Up to four non-empty strings, each at most 200 characters. Action-generated suggestions still take precedence. |
| `dictationOnly` | Boolean. Shows only the dictation control in the composer. Emits `dictation-transcript` before automatically sending the completed text. |
| `dictationTimeout` | Positive integer in milliseconds. Stops dictation after speech ends and this much silence passes. The browser can also stop recognition on its own. Recording ends after two minutes at the latest. |
| `hideActionsMenu` | Boolean. Hides ticket history and conversation reset/delete controls in the header. |
| `dictationWaveformColor` | Three- or six-digit hex color for the recording indicator, such as `#c2410c`. |

Unknown keys and invalid values are skipped with a console error; valid fields
in the same call still apply. Values are never truncated. Clearing an override
removes that key from `event.data.options`. Setting the same overrides again
does not emit a change event.

## Plain iframe integration

A plain iframe posts the same events to its parent without installing
`window.centillion`. Validate both its origin and its source. Use the iframe's
actual app origin, including the port when testing locally.

```html theme={null}
<iframe
  id="support-agent"
  src="https://app.cntillion.com/embed/YOUR_AGENT_ID"
  allow="microphone"
  title="Customer support"
></iframe>
<script>
  const iframe = document.getElementById("support-agent");
  const agentOrigin = new URL(iframe.src).origin;

  window.addEventListener("message", (event) => {
    if (event.origin !== agentOrigin || event.source !== iframe.contentWindow) return;

    if (event.data?.type === "centillion:events-ready") {
      iframe.contentWindow.postMessage({
        type: "setOptions",
        params: { messagePlaceholder: "Ask us a question" },
      }, agentOrigin);
    }
    if (event.data?.type === "dictation-transcript") {
      console.log(event.data.data.transcript);
    }
    if (event.data?.type === "optionsChanged") {
      console.log(event.data.data.options);
    }
  });
</script>
```

Register the parent listener before assigning the iframe's `src` when creating
it dynamically. Wait for `centillion:events-ready` before posting commands.
`resetOptions` uses `{ type: "resetOptions", params: { footer: true } }`;
omit `params` to clear everything. With a referrer-free iframe, this handshake
also establishes the parent origin before any conversation content is posted.

The handshake repeats when a published chat reloads after a reset. Keep the
parent listener active and resend runtime options on each
`centillion:events-ready` event, as the example does. The JavaScript embed
script reapplies its saved runtime overrides automatically.

## Check your integration

Open the chat and confirm the initial `optionsChanged` event. Send a question;
expect one `user-message` followed by one `assistant-message`. Trigger a test
action and match its call ID with the result. Dictate, then edit the draft before
sending to verify the transcript and sent-message events differ.

Change an option and check that its new value is visible when your callback runs.
Reset one field, then all fields. Finally, remove a named callback and confirm
that it stops receiving events. If events are missing, check the exact event
name, the browser console, whether the iframe loaded, and whether your origin
check includes the correct scheme and port.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.