Skip to content

Proxying the collector

The tracker talks to exactly one origin, set by data-api. Point it at a hostname you control and every request becomes first-party.

<script defer
data-site="pk_live_xxxxxxxx"
data-api="https://tt.acme.com"
src="https://tt.acme.com/t.js"></script>
Reason Effect
Ad blockers Filter lists block known analytics hostnames, not tt.acme.com. Blocked traffic is missing traffic, and it skews by audience.
Content-Security-Policy script-src 'self' and connect-src 'self' need no exception.
Latency One fewer DNS lookup and TLS handshake on the critical path.
Path Method Notes
/t.js GET The tracker bundle. Cacheable — max-age=3600, stale-while-revalidate=86400, with an ETag.
/c/:key GET The identity probe. Cacheable for 60 s.
/e POST Every hit. Never cache.
/api/send POST Only if you use the umami-compatible alias.

Geo resolution also depends on it: a Cloudflare-fronted collector reads request.cf, which describes the client Cloudflare terminated. Chaining another CDN in front can turn that into the address of your proxy. See Geo.

The simplest option, and the one that needs no code. Create a hostname in your own zone and point it at the collector.

tt.acme.com. CNAME ingest.tracing.tools.

Then:

<script defer
data-site="pk_live_xxxxxxxx"
data-api="https://tt.acme.com"
src="https://tt.acme.com/t.js"></script>

This is first-party by domain, which is what blocklists key on. It is not same-origin, so the requests still go through CORS — which the collector allows from everywhere.

With a same-origin path proxy, both directives collapse to 'self':

Content-Security-Policy
script-src 'self';
connect-src 'self';

With a CNAME subdomain you still name the host, but it is yours:

Content-Security-Policy
script-src 'self' https://tt.acme.com;
connect-src 'self' https://tt.acme.com;
  1. curl -I https://tt.acme.com/t.js200, content-type: application/javascript.
  2. curl https://tt.acme.com/c/pk_live_xxxxxxxx{"m":"hash"}.
  3. Load a page and confirm a 202 from POST /tt/e.
  4. Open Sessions and check that visits are not all coming from the same city — if they are, the client IP is not surviving the proxy.