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

# Host-page events

> Track widget, conversation and page events from your own page code, Google Tag Manager or analytics.

The chat widget runs inside a cross-origin iframe, so your page cannot read its state directly. Instead, the embed loader exposes a small API on `window.anyreach` and emits a stream of events: what the visitor did on your site, what they did with the widget, and what happened in the conversation. Every event carries the same envelope (visitor id, session, page, campaign, device), so a single record is joinable into a funnel without lookups.

Events are delivered to your own JavaScript callbacks and, optionally, pushed to a Google Tag Manager data layer. Nothing on this page is sent to Anyreach: the page and journey events exist only in your visitor's browser until your code does something with them.

## Quick start

Subscribe before the loader arrives by queueing commands on `window.anyreach`. The loader replays the queue when it loads, so listeners registered this way see the very first events (`session_started`, `page_viewed`, `widget_loaded`).

```html theme={null}
<script>
  window.anyreach = window.anyreach || [];
  window.anyreach.push(['on', 'widget_opened', function (event) {
    console.log('opened from', event.page_path, 'after', event.pages_viewed_before_open, 'pages');
  }]);
  window.anyreach.push(['on', 'conversation_ended', function (event) {
    myAnalytics.track('chat_ended', { outcome: event.end_reason, messages: event.message_count });
  }]);
</script>
<script src="https://your-host/public/embed/{widgetId}" async></script>
```

After the loader has run, `window.anyreach` is an object and the same calls work directly:

```js theme={null}
window.anyreach.on('lead_capture_form', handler); // any event name, or '*' for all
window.anyreach.identify('user_123', { plan: 'pro' });
window.anyreach.track('checkout_completed', { value: 129 });
```

`push([...])` keeps working after load, so a snippet written for the queue form never has to care which state the loader is in.

## API

| Call                                | What it does                                                                                                                                                                                                                                                                        |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `on(name, fn)`                      | Subscribe to one event name, or `'*'` for every event. Returns an unsubscribe function.                                                                                                                                                                                             |
| `off(name, fn)`                     | Remove a listener.                                                                                                                                                                                                                                                                  |
| `identify(userId, traits?)`         | Bind the anonymous visitor to your own user id. Sets `external_user_id` on every later event, persists across pages, emits an `identify` event, and passes the id and traits into the widget as conversation context so the agent knows who it is talking to. Pass `null` to clear. |
| `track(name, properties?)`          | Record a conversion or any custom event from your page code. Emits a `track` event carrying `name` and `properties`.                                                                                                                                                                |
| `config({ dataLayer, pageEvents })` | `dataLayer: true` pushes every event to `window.dataLayer` (or pass a string to name a different array). `pageEvents: false` switches off the page and journey events below.                                                                                                        |
| `getContext()`                      | The current envelope (ids, session, page, campaign, device) without an event, for stitching into your own calls.                                                                                                                                                                    |

### Google Tag Manager

```js theme={null}
window.anyreach.push(['config', { dataLayer: true }]);
```

Each event is pushed as `{ event: 'anyreach.<name>', anyreach: { ...payload } }`. In GTM, create a Custom Event trigger on `anyreach.widget_opened` (or a regex such as `anyreach\..*`) and read fields with Data Layer Variables like `anyreach.page_path` or `anyreach.conversation_id`.

## Envelope

Every event carries these fields. Ids are minted by the loader on your page, in first-party storage, so they survive across pages even in browsers that partition third-party iframe storage.

| Field                                                                                       | Meaning                                                                                                                                                                            |
| ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event`, `event_id`, `timestamp`, `sequence_number`, `schema_version`                       | Name, unique id, ISO timestamp, per-session ordering counter (sort on this, not on client clocks), and `1`.                                                                        |
| `anonymous_id`                                                                              | Stable per browser. The basis for returning-visitor analysis.                                                                                                                      |
| `session_id`                                                                                | Rolling 30-minute inactivity window.                                                                                                                                               |
| `external_user_id`                                                                          | Whatever you passed to `identify()`, or `null`.                                                                                                                                    |
| `widget_id`, `agent_id`, `conversation_id`                                                  | Which widget and agent, and the conversation in progress (`null` outside one).                                                                                                     |
| `page_url`, `page_path`, `page_title`, `page_referrer`                                      | The page at the moment the event fired.                                                                                                                                            |
| `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`                       | Captured once at session start and carried on every event in the session, even after the visitor navigates away from the landing page. Only present when the landing URL had them. |
| `device_type`, `os`, `browser`, `viewport_width`, `viewport_height`, `language`, `timezone` | Coarse device classification from the user agent, plus viewport, `navigator.language` and the IANA timezone.                                                                       |

## Events

### Page and journey

Emitted by the loader on your page; no widget interaction needed. These give you the denominator for every engagement rate. Turn them off with `config({ pageEvents: false })` if your consent setup does not cover page-level tracking.

| Event             | Extra fields                                                                                                                                                                                                                                   |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `session_started` | `is_returning`, `entry_url`                                                                                                                                                                                                                    |
| `page_viewed`     | `navigation`: `load`, `pushState`, `replaceState` or `popstate`. Single-page apps are handled by hooking the History API.                                                                                                                      |
| `page_left`       | `time_on_page_ms`, `max_scroll_depth_pct`. Fired when the tab is hidden or the page is unloaded, once per visit to the page.                                                                                                                   |
| `session_ended`   | `session_duration_ms`, `pages_viewed`, `widget_opened`, `end_reason`. A session can only be known to have ended after the fact, so this fires on the visitor's next page load once the 30-minute window has lapsed, with the old `session_id`. |

### Widget lifecycle

| Event                       | Extra fields                                                                                                                                       |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `widget_loaded`             | `load_time_ms`, `published`                                                                                                                        |
| `widget_opened`             | `trigger` (`launcher`, `suggested_question`, `default_open`), `seconds_since_page_load`, `seconds_since_session_start`, `pages_viewed_before_open` |
| `widget_closed`             | `reason` (`launcher`, `close_button`), `open_duration_ms`, `had_conversation`                                                                      |
| `proactive_message_shown`   | `messages`, `count`. The suggested-question pills shown beside the closed launcher.                                                                |
| `proactive_message_clicked` | `message`, `index`                                                                                                                                 |
| `quick_action_clicked`      | `action_id`, `label`, `action_value`. A button in a starting-message action grid.                                                                  |
| `mode_switched`             | `from`, `to` (`chat` or `voice`)                                                                                                                   |

### Conversation

| Event                                                                            | Extra fields                                                                                                                                                                                                                                                                      |
| -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `conversation_started`                                                           | `mode`, `resumed` (true when the visitor picked the conversation back up; the new leg gets its own `conversation_ended`)                                                                                                                                                          |
| `message_sent`, `message_received`                                               | `channel` (`text`, `voice`, `human_relay`), `has_attachments`. Message text is never included.                                                                                                                                                                                    |
| `first_agent_response`                                                           | `latency_ms` from the visitor's first message to the agent's first reply. Once per conversation.                                                                                                                                                                                  |
| `form_shown`, `form_submitted`, `form_abandoned`                                 | `form_kind` (`pre_conversation`, `form`, `collect_otp`, `collect_phone_number`, `collect_image`, `handoff_identity`); the pre-conversation form also carries `form_id` and, on submit, `field_count`. Resending a code or changing method is neither a submit nor an abandonment. |
| `file_uploaded`                                                                  | `mime_type`, `size_bytes`                                                                                                                                                                                                                                                         |
| `voice_call_started`, `voice_call_ended`                                         | `duration_ms`, `end_reason` (`mode_switched`, `handoff`, `disconnected`) on end                                                                                                                                                                                                   |
| `handoff_requested`, `handoff_completed`, `handoff_unavailable`, `handoff_ended` | `provider`; `agent_display_name` and `wait_ms` on completed; `duration_ms` on ended                                                                                                                                                                                               |
| `feedback_submitted`                                                             | `form_id`, `field_count`. The answers themselves are not included.                                                                                                                                                                                                                |
| `link_clicked`                                                                   | `url`. A link the agent sent, clicked inside the transcript. Fires on the click, before the widget's open-link confirmation.                                                                                                                                                      |
| `conversation_ended`                                                             | `end_reason` (`user_ended`, `agent_ended`, `inactivity`, `widget_closed`, `new_conversation`, `switched_conversation`, `disconnected`), `message_count`, `messages_sent`, `messages_received`, `duration_ms`, `last_message_from`                                                 |
| `conversation_abandoned`                                                         | `reason`, `last_message_from`. Fired alongside `conversation_ended` when the visitor closes the widget mid-conversation.                                                                                                                                                          |

### Yours

| Event      | Extra fields         |
| ---------- | -------------------- |
| `identify` | `traits`             |
| `track`    | `name`, `properties` |

## Notes

* **Dedupe on `event_id`, order on `sequence_number`.** Listeners fire synchronously in the order events happen, but if you forward them to a server, do not trust client clocks for ordering.
* **Only your page sees these events.** Anyreach does not receive page or journey data from the loader. Conversation data reaches Anyreach through the widget as it always has. Events are delivered only to the origin the loader embedded the widget from; a widget framed some other way emits nothing.
* **Consent.** The page and journey events observe scroll depth, dwell time and URLs. If your consent banner gates that kind of tracking, call `config({ pageEvents: false })` until consent is given, or simply do not subscribe.
* **Outcome classification, intent and sentiment** are produced server-side after the conversation and are not part of this stream.
