# First-Party CNAME Proxy

By default the tracker loads from `omni.karmaflow.ai`. Ad-blockers (uBlock Origin, Brave, Pi-hole, etc.) ship blocklists that include known analytics hosts — your traffic from blocker-using visitors silently disappears. A first-party CNAME — routing the tracker through a subdomain on the customer's own domain — moves the request to a hostname the blocker has no reason to block.

## Conceptual Setup

1. Customer adds a DNS CNAME: `analytics.customer.example` → `omni.karmaflow.ai`.
2. Their CDN/load-balancer (if any) forwards `Host: analytics.customer.example` to our Cloud Run service.
3. The customer updates the snippet src to `https://analytics.customer.example/p/SITE_KEY/k.js`.
4. The tracker auto-detects the new origin from `document.currentScript.src` and uses it for `/collect` and `/p/.../container.json` too — no separate config.
5. Requests look like they're going to the customer's own domain, so blocklists don't fire.

## Provisioning the CNAME

Open **Web Analytics > Custom Domain** for the site. The UI walks you through:

### Step 1: Choose Your Subdomain

A short, generic name is best (`analytics`, `metrics`, `track`). Avoid names that obviously hint at analytics from a blocklist's perspective (`google-analytics`, `gtm`, etc. — those are themselves blocked).

The UI checks the subdomain isn't already in use by another tenant before letting you proceed.

### Step 2: Add the DNS Record

You'll be shown two records:

```
analytics.customer.example  CNAME  cnames.karmaflow.ai.
_karma-verify.customer.example  TXT  karma-verify=<one-time token>
```

- The CNAME points at our managed endpoint `cnames.karmaflow.ai` (which itself resolves to the Cloud Run service via a wildcard).
- The TXT is for one-shot ownership verification — the system queries `_karma-verify.customer.example` and checks the token matches what we issued.

### Step 3: Verify

Click **Verify DNS**. The server runs:

- `dns.promises.resolveCname('analytics.customer.example')` — must include `cnames.karmaflow.ai`.
- `dns.promises.resolveTxt('_karma-verify.customer.example')` — must include the one-time token.

DNS propagation can take a few minutes. The UI shows the current resolution state and re-checks on a 30-second loop.

### Step 4: TLS Cert

Once DNS verifies, the certificate manager auto-provisions a cert for the new hostname via Google Cloud Load Balancer's managed certificates (which use Let's Encrypt). This takes 10–60 minutes depending on LB warm-up.

You can poll status from the UI; once the cert is active, the new hostname starts serving `200 OK`.

### Step 5: Update the Snippet

The site's snippet automatically switches to the new hostname on the **Sites > Snippet** page — copy + paste the updated `<script src=...>` onto the customer site. The new src looks like:

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

Once deployed, the tracker reads `document.currentScript.src`, sees the new host, and routes every subsequent call there.

## What the Server Does Differently for CNAME Requests

Almost nothing. The HTTP handlers don't look at `Host` for anything except logging. The key requirement is that `Cross-Origin-Resource-Policy: cross-origin` is set on every public asset (`k.js`, `container.json`, `/collect/*/p`) — that header was always required for the third-party-domain case and stays required for the CNAME case (the customer's own domain is still a cross-origin host relative to wherever the customer's main app runs).

The `Access-Control-Allow-Origin` header echoes the request's `Origin` header dynamically, so the same allow-list logic works whether requests come in via `omni.karmaflow.ai` or via the customer's CNAME.

## Verifying the Tracker Routes Through the CNAME

Open DevTools on the customer site. With the CNAME live, you should see:

- `GET https://analytics.customer.example/p/SITE_KEY/k.js` → 200
- `GET https://analytics.customer.example/p/SITE_KEY/container.json` → 200
- `POST https://analytics.customer.example/collect/SITE_KEY` → 202

If any of those still go to `omni.karmaflow.ai`, the snippet wasn't updated.

If `/collect/` returns from `omni.karmaflow.ai` but `/p/.../k.js` correctly loads from `analytics.customer.example`, there's a cached old version of the tracker still running. The 5-minute cache on `k.js` clears within ~5 min for visitors who hard-reload; gradual rollout is fine for the rest.

## Rolling Back

To stop using the CNAME without deleting the configuration:

1. Update the snippet back to `omni.karmaflow.ai`.
2. The CustomDomain record stays in place (so verification doesn't need to redo).
3. The DNS record can stay too — no traffic, no harm.

To fully unprovision: delete the CustomDomain record from the UI. The cert is left in place for 24 h in case you change your mind, then auto-revoked.

## Edge Cases

### Brand-name domains and EV certs

If the customer uses EV (Extended Validation) certs, our managed cert won't match their certificate authority. You can either:

- Drop EV and use the managed cert (recommended — EV badges no longer display in Chrome).
- BYO cert: upload a customer-provided cert + key, configured via the **Web Analytics > Custom Domain > Custom TLS** flow. Cost & operational overhead — call this out only if the customer asks.

### Cloudflare proxying

If the customer's domain is behind Cloudflare with the orange-cloud proxy on, Cloudflare strips the CNAME and serves directly from their edge. Disable the proxy for the analytics subdomain ("DNS only" / grey cloud) or use Cloudflare's CNAME flattening rules to ensure the request reaches us.

### Apex domain (e.g. `customer.example` without subdomain)

CNAMEs at the apex aren't legal per the DNS spec. Use `analytics.customer.example`, never `customer.example`. If the customer absolutely needs apex, they need ALIAS / ANAME records (provider-specific) or Cloudflare's CNAME flattening.

### Subdomain takeover risk

If the customer ever deletes the CNAME record but leaves the verification TXT, then later registers a different `cnames.karmaflow.ai` mapping under a different tenant, the new tenant would inherit the existing TXT verification. The UI defends against this by requiring the TXT to be re-verified every 90 days.

## Privacy Implications

A first-party CNAME makes requests look first-party for the browser too. Cookies for `analytics.customer.example` are sent by the browser even though the server treats them as anonymous. The tracker explicitly sends `credentials: 'omit'` on `fetch`, but `sendBeacon` always sends credentials — this is one of the reasons we use a `text/plain` blob to avoid CORS preflight regardless of CNAME setup.

If the customer is in a privacy-sensitive jurisdiction (DACH, etc.), the CNAME effectively moves the data path closer to first-party — many privacy frameworks treat first-party analytics differently from third-party. Talk to legal before claiming this changes the compliance posture, though.
