# Event listeners

> React to the Bookbag chat widget in the browser and to agent activity server-side. The embedded widget runs in an iframe; use webhooks for reliable server-side events.

There are two places you can react to what happens in Bookbag: in the browser around the embedded chat widget, and on your server via webhooks. This page explains which to reach for.

## Browser: the embedded widget

The [JavaScript embed](/docs/developers/javascript-embed) renders the chat as a launcher button plus a sandboxed iframe. Because the conversation UI runs inside the iframe, your page code does not see individual messages directly — the iframe isolates the chat from your DOM.

What your page **can** do is observe and control the launcher and frame elements the loader injects into your document. A robust way to react to the widget opening or closing is to watch the injected iframe's visibility:

```javascript
// The loader injects an <iframe> pointing at /widget/frame.
function onBookbagWidgetReady(cb) {
  const tryFind = () => {
    const frame = document.querySelector(
      'iframe[src*="/widget/frame"]'
    );
    if (frame) { cb(frame); return true; }
    return false;
  };
  if (tryFind()) return;
  const mo = new MutationObserver(() => { if (tryFind()) mo.disconnect(); });
  mo.observe(document.body, { childList: true, subtree: true });
}

onBookbagWidgetReady((frame) => {
  // The widget shows/hides by toggling display.
  const watch = new MutationObserver(() => {
    const open = frame.style.display !== "none";
    window.dataLayer?.push({ event: open ? "bookbag_open" : "bookbag_close" });
  });
  watch.observe(frame, { attributes: true, attributeFilter: ["style"] });
});
```

> **WHY NOT MESSAGE EVENTS?:** The embedded widget is intentionally minimal: it does not expose conversation contents to the host page, which keeps customer chats isolated from your site's scripts. For reliable, server-side awareness of conversations and leads, use webhooks (below).

## Server: webhooks are the source of truth

For anything you need to act on reliably — a lead was captured, a chat was escalated, the agent replied — use [webhooks](/docs/developers/webhooks). They are delivered server-to-server, signed, and independent of whether the browser tab is still open.

| You want to know when... | Listen for |
| --- | --- |
| A visitor leaves their email/phone | `lead.created` webhook |
| A chat is handed to a human | `conversation.escalated` webhook |
| The agent replies | `conversation.replied` webhook |
| An action/tool runs | `action.run` webhook |
| An outbound campaign goes out | `campaign.sent` webhook |

## Building your own UI?

If you are not using the embed at all but rendering chat yourself, you have full control: call [API v2 chat](/docs/api/v2/chat), parse the [streaming frames](/docs/api/v2/streaming), and emit whatever client-side events you like. The `finish` frame gives you the `conversationId`, `messageId`, and credit usage for each turn.

- [Webhook guide](/docs/developers/webhooks) — Reliable server-side events with signature verification.
- [JavaScript embed](/docs/developers/javascript-embed) — The widget loader and iframe details.
- [Streaming format](/docs/api/v2/streaming) — Per-turn metadata when you build your own UI.
