# Product analytics on ourselves (PostHog)

Last updated Oct 27, 2026 · Page: https://freebeeads.com/docs/repo/analytics · Markdown: https://freebeeads.com/docs/md/repo/analytics.md · All docs: https://freebeeads.com/llms-full.txt

On only when `NEXT_PUBLIC_POSTHOG_KEY` is set on Vercel; `web/lib/analytics.ts` is a no-op
otherwise (dev, CI, previews). Host: `NEXT_PUBLIC_POSTHOG_HOST=eu` (default, data stays in the EU)
or `us`, or a full URL for a self-hosted instance.

## Privacy posture (CLAUDE.md rule 3)

- No `identify()`, ever. No email, phone, business name or IP (`ip: false`, `person_profiles:
  "never"`). PostHog sees an anonymous device id per browser and the events below.
- Autocapture, pageviews and session recording are **off**. Only the seven explicit events fire.
- Properties are counts and enum values only. Never add free-text fields.
- The SDK is loaded lazily, so a build without the key ships zero analytics bytes.

## Events

| Event | Fired from | Properties |
|---|---|---|
| `signup_completed` | `/signup` after the account is created | — |
| `creative_uploaded` | `/creatives` after a file upload succeeds | `kind`: `image` \| `video` |
| `game_ad_created` | game editor, first save of a new game ad | `template`: `cricket` \| `spin` \| `wheel` \| `catch` |
| `booking_created` | `/book` after "Confirm & book" succeeds | `slots` (total), `days`, `screens` |
| `approval_decided` | `/ops/approvals` after approve/reject succeeds | `decision`: `approved` \| `rejected`, `ops: true` |
| `report_viewed` | campaign report opened (`/campaigns/[id]`) | — |
| `wall_paired` | `/ops/pair` when the player confirms the bind | — |

Every event also carries `app: "web"` and `build` (the Vercel commit SHA) via `register()`.

## The funnel to watch

Build one PostHog *Funnel* insight, conversion window 14 days, strict order:

1. `signup_completed`
2. `creative_uploaded` **or** `game_ad_created` ("first creative")
3. `booking_created` ("first booking")
4. `approval_decided` where `decision = approved` ("first approved")

Read it weekly. Pre-launch targets to argue about: ≥ 60 % of sign-ups upload something the same
day; ≥ 40 % of uploaders book within 14 days; approval within one working day (ops SLA, measured
on the backend, not here).

### "Days to first booking"

Because we do not identify users, this is per anonymous browser, which is fine for a self-serve
funnel. In PostHog: *Trends* → `booking_created` → breakdown "first time event" is not enough;
use the funnel above and read **time to convert** between step 1 and step 3 (median and p90). Watch
the median: if it drifts past 3 days the booking flow has a friction problem, not a demand problem.

## Also worth a dashboard tile

- `creative_uploaded` by `kind` vs `game_ad_created` by `template` — which ad formats sell.
- `booking_created` → sum of `slots` and avg `days` — order size over time.
- `approval_decided` `rejected` share — if > 15 %, the upload-time checks need to catch more.
- `wall_paired` count per week — screens coming online.

## Not here

Audience numbers, plays, revenue — those come from the ledger (`/v1/metrics/*`, `/ops/screens`
Metrics tab), never from the analytics tool.
