Installing the Tracker

Create a site, paste the snippet, configure auto-collection, and verify events are landing.

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

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:

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

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):

<script async
  src="https://omni.karmaflow.ai/p/YOUR_SITE_KEY/k.js"
  data-debug="true"
  data-wait-for-consent="true"></script>
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:

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