When your app name looks like a bot
Part 5 of 5 in the series Self-hosted Umami:
Last week I shipped BookScanner 2026.4, the first version with umami-swift analytics on board. Today the dashboard showed one visitor: a test event I had sent by hand with curl. I had used the app plenty myself since the release. Umami counted none of it.
A very quiet dashboard
My other apps report to the same self-hosted Umami backend through the same package, and their numbers ticked along fine. Only BookScanner was silent. The app got HTTP 200 on every upload, cleared its queue, and reported success. Nothing failed anywhere, nothing was logged anywhere. My favorite kind of bug.
The one working data point was that curl test. It differed from the app’s requests in a single header: the User-Agent. I sent a browser’s. The app sends its own:
bookscanner.ios/2026.4 (iPhone17,1; iOS 26.0)
Beep boop
Umami filters bots before storing anything, using the isbot library to judge the User-Agent. A request that looks like a bot gets HTTP 200 with this body, and the hit goes in the bin:
{"beep":"boop"}
isbot has 209 patterns, and one of them is (?<!cam)scan. “bookscanner” contains “scan”, so every single request from the app was classified as a scanner bot and dropped. The lookbehind means an app called CamScanner would have passed. Mine didn’t.
That also explains why the other apps were fine: birthdays.ios and olsen.ios match nothing. I ran the whole pattern list over a pile of plausible app names, and the danger zone is bigger than I expected: scan, bot, search, crawl, spider, http, monitor, news, preview, proxy, checker, download, agent, archive, capture, and any name starting with read or track. A reader app or a tracker app dies the same silent death.
The fix
Since the payload already identifies the app (website id, hostname, and app version), the User-Agent never needed the app’s name at all. From version 1.7.0, umami-swift sends a standard Safari-style agent for the platform instead, built from the real OS version and device:
Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1
No app name, so no pattern can ever match one. As a bonus, Umami can parse this agent, so the dashboard now shows real OS versions where it used to say unknown.
The already-shipped BookScanner still sends the old header, so the server needed a hand too: fly secrets set DISABLE_BOT_CHECK=1 turns Umami’s bot filter off until enough users are on the fixed version. The websites lose their bot filtering in the meantime, a trade I can live with for a few weeks.
The dashboard is no longer quiet. Beep boop.
Part 5 of 5 in the series Self-hosted Umami: