Skip to main content
How each of the seven web metrics the RFP requires (page views, unique visitors, referrers, session duration, bounce rate, country, device and browser) is produced from Umami’s tables and read routes, and the two rules that decide how visitors and visits are counted on this platform. Umami facts below are read from tag v3.5.0 (paths relative to its src/) and confirmed by the end-to-end suite tests/webpath (make webpath, plan phase 5).

Where the data comes from

Every event a browser sends is accepted by the gateway and replayed into Umami by the replayer as one POST /api/send (plan section 4.4 step 12): Umami’s session id is uuid(website, ip, userAgent, hash(dailySalt, vt), distinctId) and its visit id is uuid(sessionId, hash(startOfHour(createdAt))) unless a cache token carries an earlier visit id (lib/collection-session.ts). With the constant address, a session is one visitor token with one user agent on one website on one UTC day.

The seven metrics

All read routes take startAt and endAt in unix milliseconds and count only events whose created_at is in that range. “Pageview events” are rows with event_type 1; custom events (event_type 2) never count as views and never make a visit. The metrics bar of the overview page shows Visitors, Visits, Views, Bounce rate and Visit duration; the Sources, Location and Environment panels show referrers, countries, and browsers, OS and devices (docs/reference/umami-contract.md item 11).

Unique visitors are unique per UTC day

Two things change at 00:00 UTC, and either one alone gives a new visitor:
  1. The gateway’s daily_secret rotates and is destroyed, so the same browser gets a new visitor token vt (plan 4.4 step 8).
  2. Umami runs with SALT_ROTATION=daily and TZ=UTC, so its base salt changes with the event’s day (lib/crypto.ts getSalt, startOfDay of created_at).
Consequences:
  • A person who visits on Tuesday and Wednesday is two visitors. Visitors over a multi-day range equal the sum of the daily visitors. Measured: one visitor token on three days gives 1 visitor per day and 3 over the three days (TestWebPath/time_ranges).
  • A visit that runs across midnight splits into two visits of two visitors, because the session id changes (Umami treats a cache token of another session as drift).
  • The same browser with a different user agent (an update, another browser) is another visitor the same day. Two browsers behind one address are two visitors; one browser that changes address is a new visitor too, because the address is part of vt.
  • A restart of the whole NATS cluster during a day loses edge_secrets (memory storage), so every returning visitor of that day is counted again (plan 4.4, consequences).

The visit rule

A visit ends when the visitor is idle for more than 30 minutes, with one exception that comes from Umami’s own visit id.
  • While the gap to the visitor’s previous event on that website is at most 1800 s, the replayer sends Umami’s last cache token, and the visit continues, also across UTC hours.
  • When the gap is longer, the replayer sends no cache token. Umami then derives the visit id from the session and the UTC hour of the event. An event in a different UTC hour than the visit’s start therefore starts a new visit, but an event in the same UTC hour joins the visit that started in that hour.
All three rows are measured by TestWebPath/visit_rule. The same-hour case is Umami’s own behaviour for events without a cache token, which is how Umami treats timestamped events (historical skips its 30 minute expiry), so a visit on this platform can last longer than 30 minutes of idle time only inside the UTC hour in which it started. The gap is measured on gateway acceptance times, so a backlog replayed after an outage forms the same visits as live delivery.

Bands

Each site has a primary website (id equals the site id) and a quarantine website. Events from an address prefix go to the quarantine website from the event that brings the prefix to IPCLUSTER_THRESHOLD distinct visitor tokens for the site within an hour (internal/gateway/ipcluster.go) (plan 4.4 step 9). Every metric above is computed per website, so quarantined traffic is counted separately and never mixed into the primary numbers.

What is not counted

  • Bots: Umami’s isbot check runs on the original user agent; a bot event answers {"beep":"boop"}, stores nothing and is counted as bot on EDGE_STATS and in replayer_outcomes_total.
  • Events the gateway refused (Sec-GPC: 1, bad proof, wrong origin, rate limits); they are counted on EDGE_STATS by outcome.
  • Visits that hold only custom events: they have no pageview, so Umami’s /stats does not count them as visits, visitors or bounces.
  • Events whose timestamp is not after a website reset: Umami clamps every read to the website’s resetAt (lib/request.ts). The replayer sends whole acceptance seconds, so an event accepted in the same second as a reset, or a backlog accepted before it and replayed after it, is stored but never shown.

Daily aggregates (phase 11)

The operator views read agg.web_daily and agg.web_daily_dim, which the jobs task aggregate-web (internal/jobs/aggregateweb) fills from Umami’s own read routes, as the admin, for each active site, both bands and each UTC day: /stats gives one web_daily row, /metrics with type referrer, country, device, browser and os gives the web_daily_dim rows. A run reads today and the 3 days before it every hour; jobs run aggregate-web --from <day> --to <day> reads any range again. Each site day replaces its rows in one fenced transaction, so re-running changes nothing and a backfill restores deleted rows. The research items of plan phase 11 were measured on 2026-11-12 against the dev Umami (the pinned 3.5.0 image, events sent through /api/send the way the replayer sends them, three UTC days, six visitors, a bot, a custom event, own-host and www. referrers, an event at exactly 00:00:00) and are asserted by TestAggregateWeb (internal/jobs/aggregateweb) on every run. Consequences for the operator views (docs/reference/control-plane-api.md section 8):
  • Visitors, visits, bounces and total time over a range are sums of daily rows, which equal Umami’s answer for the same range. A project’s numbers are the sum of its sites; one person on two sites of a project is two visitors, as Umami counts per website.
  • Bounce rate is bounces / visits and average visit duration totaltime / visits, the formulas of Umami’s dashboard.
  • A breakdown value over a range is the sum of its daily values, which equals Umami’s /metrics for the range.
  • Removed tenants are not read again; the purge step agg deletes their rows unless removal_keep_aggregates keeps them.