← Wiki

Analytics without tracking anyone

Definition

A portfolio of small products needs one answer to “how many people, from where, doing what”, across landing pages and the apps they sell. superduper-analytics (open source) is one Cloudflare Worker that every site and app posts events to: no cookie, nothing kept in the browser, no identifier that follows a person across days or products. Adding a product is one registry entry and one commit; an unregistered source is refused.

Rule

One counter for every platform. Websites load a script the Worker serves itself, so changing how browsers report is one deploy. iOS apps use the SuperDuperAnalytics Swift package; a Kotlin Multiplatform app gets one client in commonMain for Android and iOS. All post the same schema to the same endpoint, so a landing visit and an app launch of one product share a source id.

<script defer src="https://<your-worker>/sda.js" data-source="<id>"></script>
<a href="https://apps.apple.com/app/id<APP_ID>?ct=site-hero&mt=8"
   data-sda-event="app_store_opened" data-sda-placement="hero">Get the app</a>

The id is derived, not stored. On the web the Worker hashes a secret salt with the visitor’s IP, user agent and the UTC date, and keeps 16 hex characters. It changes at midnight UTC, so it can count “roughly how many people today” and nothing longer. Phones change carrier IPs and share NAT, so apps send a UUID made on first launch and deleted with the app: not the advertising ID, not the account. That choice is what lets the App Store privacy label say “Data Not Linked to You”. Attach a user ID to events once, and the label has to change.

Installs are attributed by the store, per source. The App Store link carries a campaign token (ct=site-hero), and App Store Connect attributes page views, installs, purchases and subscriptions to it. Google Play does the same with a percent-encoded referrer of UTM tags, and its Install Referrer API hands the app that string on first launch. The click is our event, app_store_opened. Every link in the chain is a count per source: “the landing brought 40 installs” is answerable, “this visitor installed” is not.

Money comes from the store. A purchase_completed event can come from a test build or never arrive, so revenue is read from store sales reports and the events are checked against them.

What it cannot tell you

The reading rule that follows: a gap of a few percent between funnel steps is noise, a gap of several times is a finding.

Why PostHog was dropped

The house rule is one analytics project per product, and PostHog’s free plan allows one project (paid: six). Self-hosted PostHog is officially unsupported, needs a 4 vCPU / 16 GB machine and gets only free-tier features. Google Analytics is web-only and brings cookies; Counterscale counts page views, not app events. PostHog was retired everywhere on 26 Oct 2026 (posthog-cross-platform keeps the old setup as history). If session replay ever becomes necessary, the plan is to point PostHog at the same event stream, not to grow a worse copy here.

Numbers

Learned from

The repo’s PRD, architecture notes and metrics doc, where each limit above sits next to the incident that produced it.

Connections

Related