# Installing the Tracker

The tracker is a single JavaScript file delivered from `/p/:siteKey/k.js`. The `siteKey` is encoded in the URL so the tracker can derive it from `document.currentScript.src`, which lets the same file work behind a first-party CNAME proxy without rebuilds. The file is the same for every site, except that a site with **Wait for consent** turned on gets that one setting written into it (see [Privacy, Consent & Retention](/help/web-analytics/privacy-and-retention)).

## Step 1: Create a Site

Navigate to **Web Analytics > Sites > + New Site** (or call `POST /api/analytics/sites` directly). You'll provide:

| Field | Notes |
| --- | --- |
| `name` | Human-readable label. |
| `domain` | The primary website domain (e.g. `customer.example`). Used as the default origin allowlist entry. |
| `allowedOrigins` (optional) | Additional origins permitted to POST to `/collect/:siteKey`. Wildcard `*` is allowed. |
| `respectDnt` (default `true`) | When true, events tagged with `consent.dnt: true` or `gpc: true` are filtered server-side. |

Retention, allowed origins, Do Not Track and Wait for consent are set after creation on the site's **Configure → Settings** page (below).

The system generates a 16-character hex `siteKey` and registers it in the global `AnalyticsSiteKeyIndex`. This index lets the unauthenticated `/collect` endpoint resolve a siteKey to its tenant + database name without a session.

## Step 2: Copy the Snippet

The Sites page shows a ready-to-paste snippet. Substitute your `siteKey`:

```html
<!-- Karmaflow.ai tracker -->
<script async src="https://omni.karmaflow.ai/p/YOUR_SITE_KEY/k.js"></script>
<script>
  window.karma = window.karma || function(){(window.karma.q=window.karma.q||[]).push(arguments)};
</script>
```

Paste this into the `<head>` of every page. Order doesn't matter — the queue stub captures any calls that happen before the tracker finishes downloading; once it loads, it drains the queue and starts sending events.

The tracker:

- Initializes on `DOMContentLoaded`, or immediately if the script loads after that.
- Reads its own `siteKey` and endpoint from `document.currentScript.src`.
- Auto-fires `page_view` on init and on every `pushState` / `popstate` (SPA-safe).
- Begins binding the auto-collection listeners (clicks, forms, scroll depth, web vitals, JS errors).

## Step 3: Verify First Events

Three ways, fastest first:

1. **Realtime dashboard** (`Web Analytics > Realtime`). Reads from a per-tenant Mongo capped collection — events should appear within ~2 s of being fired.
2. **Browser console**. Add `data-debug="true"` to the snippet's `<script>` tag to enable log lines like `enqueued page_view`, `flushing 5 events via batch-full`.
3. **DevTools Network tab**. Look for `POST /collect/:siteKey` returning `202 Accepted` with `{"success":true,"accepted":N}`.

If you see traffic in Realtime but not in Funnels / Cohorts the next day, see **Fleet Dashboard & Troubleshooting** — the most common cause is BigQuery insert errors landing in `events_dlq`.

## Configuring the Tracker

Most sites need no options. The ones that exist can be set with a `data-*` attribute on the snippet's `<script>` tag, or with an `init` call placed in the stub **before** the tracker loads (the tracker starts itself as soon as it loads, so a later `init` is ignored):

```html
<script async
  src="https://omni.karmaflow.ai/p/YOUR_SITE_KEY/k.js"
  data-debug="true"
  data-wait-for-consent="true"></script>
```

```js
karma('init', 'YOUR_SITE_KEY', { debug: true, waitForConsent: true });
```

| Option | `data-*` attribute | Default | Behavior |
| --- | --- | --- | --- |
| `respectDnt` | `data-respect-dnt="false"` turns it off | `true` | Skips any event when `navigator.doNotTrack === '1'` or `navigator.globalPrivacyControl === true`. Enforced in the browser and again on the server. |
| `waitForConsent` | `data-wait-for-consent="true"` | `false` | Sets no cookie and records nothing until your cookie banner calls `karma('consent', { analytics: true })`. Prefer the site setting below; this attribute and option can only turn waiting **on**, never off. |
| `autoTrack` | `data-auto-track="false"` turns it off | `true` | Automatic page views, clicks, scroll depth, form submits, web vitals and JS errors. With it off, only the events you send yourself are recorded. |
| `debug` | `data-debug="true"` | `false` | Verbose `console.log` of every queue / flush / tag-eval step. |
| `endpointBase` | — | snippet URL origin | Override only when running behind a CNAME proxy that the tracker can't auto-detect. |

### Site settings

After a site is created, its privacy and origin settings live on the site's own page: open **Data → Web Analytics**, pick the site, then **Configure → Settings** (needs the `analytics:read` permission to see the page). It holds **Wait for consent**, **Respect Do Not Track**, the name, domains and **Allowed origins** (one per line, matched exactly, so `https://example.com` and `https://www.example.com` are separate entries; the page warns when one is missing). A change reaches visitors' browsers within about 10 minutes.

## Setting Up a First-Party CNAME (Optional but Recommended)

To bypass ad-blockers, route the tracker through a subdomain you control (e.g. `analytics.customer.example` → `omni.karmaflow.ai`). See **First-Party CNAME Proxy** for the full DNS verification flow. With a CNAME live, the snippet src becomes:

```html
<script async src="https://analytics.customer.example/p/YOUR_SITE_KEY/k.js"></script>
```

The tracker auto-detects the new origin and routes its `POST /collect/:siteKey` calls to `https://analytics.customer.example/collect/...` — no code changes on the customer site.

## What Gets Set Up Automatically When You Create a Site

When you `POST /api/analytics/sites`:

1. A new `Site` document is created in the tenant Mongo DB.
2. A `siteKey ↔ tenantId` row is inserted into the global `AnalyticsSiteKeyIndex` so `/collect` can resolve siteKey without a session.
3. A default `Main` workspace is created for Tag Manager work.
4. An empty container snapshot is published (so `/p/:siteKey/container.json` returns a valid empty container immediately).
5. Per-site Mongo collections are lazily created on first event (`realtimeBufferModel`, `eventRollupHourlyModel`, `eventRollupDailyModel`).

The BigQuery `events`, `events_dlq`, and `identity_map` tables are created once per Cloud Run instance on first event ingest via `ensureEventsTable()`. They are NOT per-tenant — every tenant writes to the same tables, isolated by mandatory `WHERE tenant_id = ?` clauses in the query layer.

## Uninstalling

Remove the snippet from the customer site. To stop the siteKey from being usable even if the snippet is still present somewhere (e.g. cached HTML), set the Site's `status` to `paused` — `/collect/:siteKey` will then return `202 { accepted: 0, filtered: N }` without writing anywhere. Archiving the site (`status: archived`) preserves history but rejects new events with `404 unknown_site_key`.
