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

# Analytics Tracking (GA4)

> Google Analytics 4 in the web app: the typed event helper, the funnel events, privacy rules and per-environment setup.

## Overview

The web app (`apps/web`) sends Google Analytics 4 page views and a fixed set of
product events. They measure the growth funnel:

**sign up → verify email → agent created → agent published → public chat → purchase**

GA4 is off unless a superadmin sets a measurement id in System Config. It does not replace KB
analytics, the chat timing framework or the billing ledger. Use the ledger for
revenue and credits: browser analytics drop hits.

The full taxonomy — every event, its params and the file it fires from — is in
the repository at `docs/architecture/ANALYTICS_TRACKING.md`.

## Sending an event

Every event goes through `trackEvent()` in `apps/web/src/lib/ga.ts`. The
`GaEvent` union types each event name to its params, so an unknown event or a
wrong param is a compile error.

```ts theme={null}
import { trackEvent } from "@/lib/ga";

await api.createKnowledgeBase(input);
trackEvent({ name: "kb_created", params: { kb_id: kb.id } });
```

* Call it once, at the existing success point, after the `await` that succeeded.
* When the next line is a full-page navigation (`location.assign`), use
  `trackEventAfterNavigation()`. The event is stored in `sessionStorage` and the
  next page sends it.
* Before the id arrives from `/api/client-config`, calls are queued (cap 50).
  Without an id, during SSR and on `/embed/*`, nothing is sent. A call never
  throws.

To add an event: add a member to `GaEvent`, add the call, and add a row to the
taxonomy table in `ANALYTICS_TRACKING.md`.

## Events

| Event | Funnel stage |
| - | - |
| `sign_up`, `login`, `email_verified`, `invitation_accepted` | Acquisition |
| `agent_creation_started`, `agent_creation_step`, `agent_created` | Activation |
| `prompt_config_published`, `agent_published`, `kb_created`, `document_uploaded` | Activation |
| `channel_distributed` | Distribution |
| `public_chat_started`, `public_chat_message_sent`, `hitl_escalated` | Engagement |
| `purchase` (GA4 ecommerce) | Revenue |

Mark `sign_up`, `agent_created`, `public_chat_started` and `purchase` as GA4 key
events.

## Privacy

* Never send an email, a name, message content, prompt text, a document name or
  a URL with a query string.
* `user_id` is the internal user UUID. `setGaUserId()` sets it from
  `AuthContext` and clears it on logout.
* Never send the anonymous visitor id (`anonymousId`) in any param. It is a
  bearer credential: the public transcript endpoints accept it as their only
  access check, so a GA row carrying it would let anyone with GA access read
  that visitor's transcripts. GA's own `client_id` counts visitors.
* `purchase` revenue is sent from the browser, so anyone can spoof it. The
  billing ledger is the source of truth for revenue. `transaction_id` is the
  Stripe Checkout session id; GA uses it to de-duplicate purchases. It is not a
  credential: no app endpoint accepts it.
* Page views are sent by the app with `page_location` and `page_referrer`
  reduced to origin + path, because sign-in, invitation and password-reset
  tokens travel in query strings.
* GA never runs on the embed widget (`public/embed.js`, `/embed/*`), which
  renders on customer sites.

<Warning>
  There is no consent banner and no GA4 Consent Mode yet. That is a pending
  product decision, and it must be settled before GA runs for EU/UK visitors on
  the live host.
</Warning>

## Setup

Each environment has its own GA4 web stream. A superadmin sets its measurement
id in **Admin → System Config → Analytics** (`GA_MEASUREMENT_ID`). The value is
stored in the platform config, not in `.env`, and needs no rebuild. The web app
reads it from the public `GET /api/client-config` once per page load, so a
change takes effect within two minutes (the server caches the value for 60
seconds, and browsers cache the response for 60 seconds). A value that is not
`G-` followed by 4 to 20 capital letters or digits is rejected on save. Clear
the value to turn GA off.

In each GA4 web stream:

1. Turn off **Enhanced measurement → Page views → Page changes based on browser
   history events**. The app sends its own page views.
2. Turn off **Enhanced measurement → Site search**, **Form interactions** and
   **Outbound clicks**. gtag.js runs them outside the app's URL sanitiser, and
   Site search reads the `q`, `s`, `search`, `query` and `keyword` URL
   parameters — the app uses `?q=` and `?search=` for search boxes.
3. Turn on **Redact data → Query parameters** for `token`, `returnUrl`,
   `session_id`, `q`, `s`, `search`, `query` and `keyword`.

The per-environment measurement ids and the full checklist are in
`docs/architecture/ANALYTICS_TRACKING.md`.


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