# Events

The widget posts CustomEvents to the `window` so you can wire analytics, side effects, or your own UI changes when the agent state shifts.

## Quick example

```ts title="track.ts"
window.addEventListener("livelayer:opened", () => {
  analytics.track("agent_opened");
});

window.addEventListener("livelayer:conversation:started", (e) => {
  analytics.track("conversation_started", {
    conversationId: e.detail.id,
  });
});
```

Every event is a `CustomEvent`; payload (if any) is on `event.detail`.

## Event catalog

<EventTable events={[
  { name: "livelayer:loaded",                 description: "Widget script booted and the <livelayer-widget> element is mounted. Fires once per page load." },
  { name: "livelayer:opened",                 description: "Visitor clicked the bubble to expand the widget panel. Does not imply a conversation has started." },
  { name: "livelayer:closed",                 description: "Visitor closed the panel back to a bubble." },
  { name: "livelayer:conversation:started",   description: "Session connected; agent and visitor can speak. Fires once per session.", payload: "{ id: string; agentId: string }" },
  { name: "livelayer:conversation:ended",     description: "Session ended (visitor left, timeout, or programmatic close).", payload: "{ id: string; durationSeconds: number }" },
  { name: "livelayer:agent:state",            description: "Agent state changed: idle | listening | thinking | speaking. Useful for syncing your own UI cues.", payload: "{ state: \"idle\" | \"listening\" | \"thinking\" | \"speaking\" }" },
  { name: "livelayer:transcript",             description: "New transcript line(s) available.", payload: "{ entries: Array<{ id, role, text, final }> }" },
  { name: "livelayer:error",                  description: "Recoverable error. Codes include MIC_PERMISSION_DENIED, AGENT_TIMEOUT, key_wrong_org.", payload: "{ code: string; message: string }" },
]} />

## Patterns

**Sync your loading UI:**

```ts title="loading.ts"
const spinner = document.querySelector("#agent-spinner");

window.addEventListener("livelayer:agent:state", (e) => {
  spinner?.classList.toggle("active", e.detail.state === "thinking");
});
```

**Pipe transcript to your own log:**

```ts title="transcript.ts"
window.addEventListener("livelayer:transcript", (e) => {
  for (const entry of e.detail.entries) {
    if (!entry.final) continue;
    console.log(`[${entry.role}] ${entry.text}`);
  }
});
```

The widget surfaces both partial and final entries; filter on `entry.final` to log only finished sentences.

**Show a bespoke error toast:**

```ts title="error-toast.ts"
window.addEventListener("livelayer:error", (e) => {
  if (e.detail.code === "MIC_PERMISSION_DENIED") {
    showToast("We need mic access to talk. Click the 🎤 icon in your address bar.");
  }
});
```

## Programmatic control

Beyond listening, you can interact with the widget element directly:

```ts title="control.ts"
const widget = document.querySelector("livelayer-widget");

// Open the bubble panel
widget?.dispatchEvent(new CustomEvent("livelayer:open"));

// Close it
widget?.dispatchEvent(new CustomEvent("livelayer:close"));

// Swap the agent
widget?.setAttribute("agent-id", "agt_xyz789");
```

For richer programmatic control (publishing data over the agent's data channel, controlled session, custom auth), use the [NPM package](/docs/develop/npm/overview).
