← All insights

Your analytics says 172 visitors. 150 of them are one bot.

On 12 October, the self-hosted Umami instance for one of our portfolio products recorded 172 sessions — a 3× spike over its weekly average, exactly the kind of chart that gets forwarded with a 🎉. It wasn’t a launch. 150 of those sessions came from one automated fleet that executes JavaScript, fires the analytics beacon like a human browser, and leaves fingerprints only if you know where to look. This is how our daily agent caught it, and what we changed.

The short version

“Runs JavaScript” stopped being a bot filter: headless fleets fire your client-side beacon just fine. The reliable tells are distributional — square screen sizes like 1366×1366, one country dominating, near-100% bounce with zero engaged time, and a crawl-shaped long tail of single-hit URLs. The strongest detector is a second, independently-placed beacon: when client-side and server-side counts disagree 172 vs 38, the gap is the bot count. Block at the network edge by TLS fingerprint, not in the analytics UI.

The fingerprints, in the order we found them

Square screens. Of the 172 sessions, 150 reported a screen of 1366×1366 pixels. No consumer device is square; 1366×1366 is what you get when a headless browser is launched with a default width and the height set equal to it. Smaller clusters at 2000×2000 and 1280×1280 appeared on other days — same tell, different fleet configuration. Screen size is self-reported by the page’s own JavaScript, which is exactly why operators forget to fake it.

One country, one browser. 152 of the sessions geolocated to Singapore and 165 identified as Chrome. Real audiences are mixtures; fleets are monocultures rented from one data-centre region.

Zero engagement, crawl-shaped paths. The fleet’s sessions bounced at ~100% with no measurable time on page, and the URL list was a long tail of single hits across thousands of programmatic pages — alphabetical-crawl behaviour, not navigation. Humans cluster on a few pages; crawlers visit everything once.

The real detector: two beacons that disagree

The same product also has server-side analytics at its hosting edge. For the same day it counted 38 visitors against Umami’s 172. Two measurement points placed differently — one in the page, one in the infrastructure — rarely agree exactly, but they should agree on the shape. A 4.5× disagreement on a spike day is not measurement noise; it is the bot count, read directly.

One caution from the same month: a different fleet, proxying real Chrome, managed to fake beacons to the server-side product too. Neither instrument is trustworthy alone. Agreement between independent instruments is the signal — the same principle we apply to data pipelines, applied to marketing data.

Where the fix belongs

We did not “filter bots in the dashboard.” Analytics-side exclusion (segmenting out the square-screen fingerprint) is useful to recover the human series — about 20 real visitors a day, which matches the server-side count — but it treats the symptom. The block belongs at the network edge, where the fleet’s TLS handshake fingerprint (JA4) identifies the automation stack regardless of the User-Agent it claims. Our WAF now denies on those fingerprints; the analytics cleaned themselves up the following day.

Why it matters beyond vanity: every downstream decision — A/B tests, SEO channel attribution, “did the launch work” — silently breaks when 87% of a spike is synthetic. Our daily SEO agent now cross-checks the two instruments before crediting any traffic change to a channel, and treats disagreement as a finding in itself.

Update, 18 October: the fleet goes residential

Six days after publishing, the same fleet came back 18× larger — 702 “visitors” in one day against a human baseline of around 60 — and the country tell was gone. Singapore now accounted for only 41% of sessions; the rest arrived from residential-proxy-heavy regions (Brazil 66, Vietnam 48, Nigeria, Bangladesh, Pakistan, a long tail of 20+ countries). A geo filter written against the first wave would have missed most of the second.

What survived the operator’s upgrade: 89% of sessions still reported the square 1366×1366 screen, 100% were Chrome-on-laptop in English, every session arrived with no referrer, fired no custom event, and bounced. The network-level identity was laundered; the device-level fingerprint and the behavioural monoculture were not. The practical lesson is that no single tell is stable — fleets patch whichever one got them blocked — so detection has to read several independent dimensions jointly, and the ones self-reported by the page’s own JavaScript (screen, referrer, engagement) are patched last.

Metrics you can’t trust are worse than no metrics

EBD Sweden builds analytics and AI-agent pipelines that cross-check their own inputs — so the number on the dashboard is one you can act on.