Reference
How we count
Analytics tools disagree about basic numbers because they define them differently and rarely say how. Here is every definition Vitrus uses, with the query that implements it.
Pageview
One row with type = 'pageview'. A client-side route change in a SPA
counts; navigating to the same path twice in a row does not.
Visitor
A distinct visitor_id within the window, where
visitorId = sha256(secret_salt + "|" + day + "|" + site + "|" + ip_prefix + "|" + user_agent)
Session
Events from the same visitor, with no gap longer than 30 minutes. The 31st minute starts a new session. This is the industry convention and matches GA4's default.
Bounce rate
The share of sessions containing exactly one pageview.
SELECT ROUND(100.0 * SUM(CASE WHEN views = 1 THEN 1 ELSE 0 END) / COUNT(*), 1)
FROM (SELECT session_id, COUNT(*) AS views
FROM events
WHERE site_id = ? AND ts >= ? AND ts < ? AND bot_kind = '' AND type = 'pageview'
GROUP BY session_id)
Note what this means: a session with one pageview and five custom events still bounces. Someone who reads a long article and leaves counts as a bounce — a high bounce rate on a blog is normal, not a problem.
Visit duration
The span between a session's first and last event. A single-event session counts as 0 seconds and is included in the average. Excluding them would push the number up and it would no longer deserve the name "average". Umami and Litlyx do the same.
Entry and exit pages
A session's first and last pageview — window functions over
ts, with the row id as a tie-breaker so the result is stable when two
events share a millisecond.
Channel
Resolved in a fixed order: internal → AI utm → referrer host table → utm_medium → bare utm_source → direct. The referrer is checked before utm_medium on purpose: a referrer is something we observed, a utm tag is a claim made by whoever wrote the link. Observation beats assertion. See channels.
Core Web Vitals
The 75th percentile, never the average — a handful of slow visits drags a mean around and manufactures a "nothing is wrong" reading. Google's own thresholds are defined on p75. Each metric is sent once per page, on exit, because LCP and CLS keep changing during a visit.
Retention
Requires identified users. It cannot be computed on anonymous traffic, because the visitor id changes daily. Rather than approximate it, the API returns a reason and a remedy. See retention.
Time zone
Everything is bucketed in UTC, including the daily salt boundary. One consequence worth knowing: if you are in UTC+3, your "day" in the dashboard starts at 03:00 local time.
What counts against quota
Pageviews and custom events, equally. Web Vitals and error reports are custom events, so they count too. Bot requests do not.