Installing the Tracker

The tracker is a single 30 KB JavaScript file delivered from /p/:siteKey/k.js. The file is byte-identical for every site; the siteKey is encoded in the URL so the tracker can derive it from document.currentScript.src. This lets the same file work behind a first-party CNAME proxy without rebuilds.

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.
analyticsRetentionDays (optional) Per-site override of the tenant default (25 months). null = unlimited.

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:

<!-- KarmaFlow 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:

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. Set window.karmaDebug = true before the snippet loads 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

The snippet has three optional config knobs. Pass them via a script-tag data-* attribute or by calling karma('init', {...}) before the first event:

<script async
  src="https://omni.karmaflow.ai/p/YOUR_SITE_KEY/k.js"
  data-karma-respect-dnt="true"
  data-karma-debug="false"></script>
Option Default Behavior
respectDnt true Skips enqueueing any event when navigator.doNotTrack === '1' or navigator.globalPrivacyControl === true. Both client- and server-side enforced.
debug 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.

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.exampleomni.karmaflow.ai). See First-Party CNAME Proxy for the full DNS verification flow. With a CNAME live, the snippet src becomes:

<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.