Create a site, paste the snippet, configure auto-collection, and verify events are landing.
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).
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.
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:
DOMContentLoaded, or immediately if the script loads after that.siteKey and endpoint from document.currentScript.src.page_view on init and on every pushState / popstate (SPA-safe).Three ways, fastest first:
Web Analytics > Realtime). Reads from a per-tenant Mongo capped collection — events should appear within ~2 s of being fired.data-debug="true" to the snippet's <script> tag to enable log lines like enqueued page_view, flushing 5 events via batch-full.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.
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. |
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.
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.
When you POST /api/analytics/sites:
Site document is created in the tenant Mongo DB.siteKey ↔ tenantId row is inserted into the global AnalyticsSiteKeyIndex so /collect can resolve siteKey without a session.Main workspace is created for Tag Manager work./p/:siteKey/container.json returns a valid empty container immediately).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.
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.