For three days in October, one of my apps had the best traffic of its life. Roughly 34,000 sessions, about 90% of the month's page views, nearly all of them landing on one page and leaving. My host's analytics scored them as human. Every event tile on my dashboard went up and to the right.
The North Star didn't move at all.
That wasn't luck. The North Star was a query over the rows real people create when they actually use the product: things saved, lists built. Bots don't do that. They load a page and leave. So the one number I'd decided to trust was the one number the spike couldn't touch.
Client events are wrong in both directions
It's tempting to think of analytics events as a slightly noisy version of the truth. They're not. They're biased, and the bias runs both ways.
They overcount. Bots, retries, double-fired handlers and events that fire on the wrong moment all inflate them. Most bot filters check the user agent, and over half of bad bots now pose as real browsers (Imperva, 2025). The spike that wrecks your month walks straight through.
They undercount. Ad blockers drop them: independent measurements found 6% to 26% of ordinary audiences blocking analytics, and far more on a technical crowd. Pages that never finish loading drop them. So do pages people skip.
That last one cost me a real number. On a fantasy league with a paid season pass, the "payment completed" event recorded 12 purchases. The payment provider recorded 29. The event fired from the success page, and buyers don't always land there, which Stripe says plainly is why fulfillment needs webhooks. My revenue tile was reading 41% of the truth, and it could only ever undercount.
The split: people from tables, attempts from events
None of this means throwing out event analytics. It means giving each source the job it's good at.
| Question | Source | Why |
|---|---|---|
| How many people did the core thing? | Product tables | Only real users create rows |
| How much money came in? | Payment webhook, written to a table | The provider is the source of truth, not your success page |
| Who signed up? | Server-side, at the moment it happens | Not a client event that might never fire |
| Where do people drop off? | Client events, by distinct session or user | Funnels and attempts are what events are good at |
| Did someone come back next week? | A persistent anonymous id on every row | A 30-minute session id can't answer it |
The North Star belongs in the first row. Write it as a SQL query over tables that carry a user id and a timestamp, run it, and write down the first reading with a date. If you can't write the query against today's schema, the metric isn't measurable yet, however good it sounds in the strategy doc.
The five-minute check
Here's how to find out whether your event tiles can be trusted:
- Pick last week.
- Count distinct users who did the core action, from the table.
- Count distinct users who fired the matching event.
- Compare.
If they differ by more than about 10%, find out why before you trust any tile built on that event. In my experience the answer is always one of: a bot spike, an ad-blocked audience, an event wired to the wrong moment, or analytics that was switched off at the provider while everything in the code looked fine.
The exception
There's one case where events are a fine people count: a closed, logged-in audience. If every event row carries an authenticated user id and anonymous traffic can't write to the table, a distinct count over events is a count of people. One of my invite-only apps measures its North Star exactly that way, and it's honest. The rule is about public, anonymous traffic, which is most of what small products get.
What this changes
Counting from tables made my numbers smaller, and it made them true. It also made the bot spike a non-event: an interesting chart, not a reason to rewrite the roadmap.
If you're choosing a North Star now, the North Star Metric skill walks through it, and the Metrics playbook entry covers the wider hierarchy. The checklist I run before launching anything small is in Building Small Apps.
Sources
- Imperva, 2025 Bad Bot Report, on the share of bad bots imitating real browsers.
- Marko Saric, "How many people block Google Analytics?", blocking rates across three audiences (2020).
- Plausible, "Google Analytics is missing data", blocking measured on a Hacker News traffic spike (2021).
- Stripe, Fulfill orders, on why fulfillment relies on webhooks rather than the success page.
Related services
Frameworks for this topic
Skills for this topic
Read next
Every one of my small apps that proposed an A/B test got the same answer once someone ran the math: months, sometimes longer than the season. The '1,000 visitors a week' rule of thumb is the reason. Traffic alone doesn't decide whether a test can finish.
A fractional CPO costs $250-400 per hour or $12,000-25,000 per month. The rate is not about seniority for its own sake. It is about scope, and most teams asking for a CPO need a Head of Product.
Improving a mature product is harder than building a new one, because every workaround is load-bearing. How I run discovery on products people already use every day, with stories from Home Depot and a public learning platform.
Want to work together?
I help teams ship better products. Let's talk about your situation.
Get in touch