Bounce Rate, Time on Page, Sessions: How Plausible, Matomo, Umami & PostHog Calculate Them Differently
“Bounce rate is 47%.” Compared to what? Calculated how? In which tool? The same visitor doing the same thing on the same page can register as a bounce in one analytics tool and an engaged session in another. The numbers don’t agree because the definitions don’t agree.
This guide is the side-by-side spec sheet that doesn’t exist anywhere else: how Plausible, Matomo, Umami, and PostHog actually compute bounce rate, time on page, session duration, pageviews, unique visitors, and engaged sessions. With the source-code references, so you can verify. And what to do when your dashboard says one thing and your gut says another.
Why definitions matter more than you think
Three real situations where the same data tells different stories depending on the tool:
- A blog post visitor reads for 8 minutes, then closes the tab. Universal Analytics: bounce. Plausible: bounce. Matomo: bounce. GA4: engaged session (because 10+ second threshold). Umami: bounce. The user’s behaviour was identical — the dashboard reads diverge by ~30 percentage points across tools for content-heavy sites.
- A SaaS user logs in, completes a task, signs out. Total session ~3 minutes. Average time on page in Plausible: 0 seconds (Plausible doesn’t track time-on-page for the last page in a session because there’s no second pageview to measure against). GA4: 3 minutes. Matomo: 3 minutes if you enable heartbeat, 0 otherwise.
- An e-commerce site reports “30K unique visitors this month”. Plausible: 30K unique device-day combinations using rotating SipHash daily salt. Matomo with cookieless mode: 30K unique device-day combinations with similar salting. Matomo with cookies: 30K unique cookie IDs (cookies last 13 months by default, so a returning visitor is the same unique). The cookie version is ~20-30% lower than the cookieless version for any site with returning users.
These aren’t bugs. Each tool made a deliberate choice. Knowing those choices is the difference between “our content is failing” and “we measure it differently than Google does”.
Bounce rate
The most argued metric in web analytics, and probably the most misunderstood.
Plausible
A bounce is a session containing exactly one pageview. Period. No time threshold, no scroll depth, no engagement modifier. From Plausible’s own writeup and the implementation in their open-source repo: a session is defined as activity from the same visitor (identified by their daily-rotating SipHash hash) within a 30-minute idle window. If that session has one pageview, it’s a bounce. Two or more, it’s not.
The honest part: Plausible’s bounce rate is high for content sites. A user who reads your entire 4000-word post for 12 minutes is a bounce. That looks bad if you’re benchmarking against GA4. It’s the same as Universal Analytics’s old bounce rate, which is to say it’s the original definition before Google complicated it.
Matomo
Same as Plausible: a visit with one pageview is a bounce. Configurable session timeout (default 30 minutes). Heartbeat tracking (setHeartBeatTimer()) does not change the bounce calculation — it adjusts time-on-page, not session structure. If you enable Matomo Goals or Ecommerce, a session with a single pageview but a goal completion is no longer a bounce (it’s a “single pageview visit with conversion”).
Source: core/Tracker/Visit.php in the Matomo repo. The isBounce determination happens at session aggregation time, based on the recorded actions count.
Umami
Umami’s bounce is a session with one pageview or less. The “or less” matters: Umami records page exits as session-end events without a pageview, so a single-pageview session that lasts 10 minutes and ends with the user closing the tab is a bounce. The threshold lives in queries/analytics/getWebsiteSessionStats.ts in the Umami repo — it counts sessions where views = 1.
One Umami quirk: their default session timeout is 30 minutes but is configurable via SESSION_TIMEOUT environment variable. Short timeouts inflate bounce rate; long timeouts collapse separate visits into one session.
PostHog
PostHog is the outlier. They don’t natively expose “bounce rate” as a metric in the way Plausible/Matomo do. Their session model is event-stream-based: a session is a sequence of events from the same session_id (cookie-set or in-memory depending on persistence). To compute bounce rate, you write a query against events grouped by session_id with a filter on $pageview event count = 1.
This means PostHog can give you bounce rate by any custom dimension (campaign, user property, feature flag) but you have to build the query. The default dashboards don’t have a single-number “bounce rate” tile.
GA4 (for comparison)
GA4 redefined bounce rate. In GA4, bounce rate = 1 – engagement rate. Engagement rate is the percentage of “engaged sessions”. An engaged session is any session that lasted longer than 10 seconds, had a conversion, or had 2+ pageviews. So a user who reads your 4000-word post for 12 minutes is an engaged session in GA4 (12+ seconds) and a bounce everywhere else. This is why GA4 bounce rates look so much lower than the rest.
Practical comparison
| Tool | Bounce definition | Typical bounce rate for blog | Configurable threshold? |
|---|---|---|---|
| Plausible | 1 pageview in session | 60-75% | No |
| Matomo | 1 pageview, no conversion | 55-70% | Session timeout only |
| Umami | 1 pageview in session | 60-75% | Session timeout only |
| PostHog | Computed via query | (custom) | Fully flexible |
| GA4 | 1 – (engaged sessions / sessions) | 30-50% | Engagement threshold (10s default) |
Practical takeaway: if you switched from GA4 to Plausible and your bounce rate jumped 25 points, nothing changed about your content. The definition changed. Track bounce rate as a trend line, not an absolute number to benchmark across tools.
Time on page
The most-asked, least-accurate metric in the entire stack.
The hard truth: no tool measures time on page accurately
Browser standards don’t give you a “the user just closed the tab at this exact time” event reliably. beforeunload exists but is unreliable on mobile and increasingly hidden by browsers for privacy reasons. visibilitychange fires when the tab is backgrounded but doesn’t tell you when the user comes back.
Every analytics tool fakes it with one of two strategies:
- Pageview-delta method. Measure time between pageviews in the same session. Time on page A = timestamp(pageview B) – timestamp(pageview A). For the last pageview in a session, there’s no second pageview, so time-on-page is undefined (often counted as 0).
- Heartbeat method. Send a “still here” ping every N seconds while the user is active. Time on page = number of heartbeats × interval. Catches the last-page case but adds traffic.
Plausible
Pageview-delta method, exclusively. From the Plausible docs and confirmed in the plausible/analytics repo: time-on-page is computed at query time as the difference between successive pageview timestamps within the same session. The last pageview in a session contributes 0 to the average. This means Plausible underestimates time-on-page for content sites where most sessions are single-pageview reads — the most-engaged readers are weighed at 0.
Matomo
Pageview-delta by default. Optional heartbeat via setHeartBeatTimer(15) in the tracker (records “user still active” every 15 seconds, configurable). When heartbeat is enabled, Matomo can compute accurate time-on-page even for the last pageview in a session. The trade-off: ~2× the traffic to your Matomo server (every 15 seconds for every active visitor).
Heartbeat is off by default in Matomo. Most production installs leave it off. So most Matomo dashboards show the same underestimate that Plausible does.
Umami
Pageview-delta. Same caveats as Plausible. Umami v2.x added optional “page leave” tracking via visibilitychange hooks, but it’s beta and not in the default tracker.
PostHog
Session-replay-aware. PostHog’s primary product is event tracking and session replay; their time-on-page derives from the timestamp difference between the first event and the last event in a session. With session replay enabled, they get richer signals (scroll, click, mouse movement) but the time-on-page number is still constrained by the last-event timestamp.
What “good” time-on-page looks like
| Content type | Realistic time-on-page (engaged read) | What dashboards show (pageview-delta) |
|---|---|---|
| 200-word news brief | 30-90s | ~40s (when measurable) |
| 800-word blog post | 2-4 min | ~1 min (heavy underreporting on single-page reads) |
| 2500-word technical tutorial | 5-12 min | ~2 min (severe underreporting; most sessions are single-page) |
| SaaS app dashboard | 3-15 min | Closer to truth (multi-page sessions) |
The lesson: time-on-page is a relative signal, not an absolute one. Use it to compare page A vs page B in the same tool, not to claim “users spend X minutes”. And if accurate time-on-page matters for your business case (paywalls, ad inventory, content rec algorithms), turn on heartbeat in Matomo or use scroll-depth events in any of the four.
Session / visit duration
Time between the first event and last event in a session. Less prone to the last-page problem (because session-end is implicit in the next session starting), but still depends on the session-timeout window.
| Tool | Session timeout default | How “session end” is determined |
|---|---|---|
| Plausible | 30 minutes | 30 min idle between hits |
| Matomo | 30 minutes | 30 min idle, configurable per site in admin |
| Umami | 30 minutes | 30 min idle, SESSION_TIMEOUT env var |
| PostHog | 30 minutes | 30 min idle by default, configurable via session_recording.session_idle_threshold_ms |
| GA4 | 30 minutes | 30 min idle |
The 30-minute window is the industry standard. The honest gotcha: a user who reads for 25 minutes, takes a coffee break for 35 minutes, comes back and reads for another 20 minutes will register as two sessions in every tool above. Average session duration won’t show the combined 45-minute reading time.
Pageviews
The most boring metric and the least-controversial — with one exception. All four tools count a pageview as one HTTP-style event fired when the page loads (or, for SPAs, when the route changes if you’ve wired up history.pushState tracking).
The exception is SPAs. Plausible and Umami both require you to call their trigger('pageview') or equivalent on route changes — the default tracker only fires on initial page load. Matomo’s auto-tracker does heuristic detection for some frameworks. PostHog auto-tracks pushState by default. Different defaults mean different totals on the same SPA.
And: pageviews count fired pageviews. Bot filtering (every tool has it but the rules differ) and ad-blocker drops mean every tool’s pageviews are lower than your server’s access log by 5-40% depending on audience.
Unique visitors
This is where definitions get philosophical.
The two universes
Cookie-based unique-visitor counting (GA4, Matomo with cookies, Universal Analytics historically): one cookie ID = one unique. Cookie lasts up to 13 months. A returning visitor is the same unique for the whole period.
Cookieless unique-visitor counting (Plausible, Matomo cookieless, Umami, Fathom): no cookie. Visitors are identified by a daily-rotating hash of IP + user-agent + domain + secret salt (the SipHash pattern). A returning visitor next day is a new unique, because the salt rotated.
This is a fundamental difference. On the same site with the same audience:
- Cookie-based: 30K monthly uniques (because returning visitors collapse into the same ID)
- Cookieless: 50K monthly uniques (because each daily visit from a returning user counts separately)
The cookieless number is bigger, but it’s not “wrong” — it’s measuring something different. Cookieless gives you “unique-device-days” rather than “unique devices over period”. If you need true unique-devices over a month, you need cookies (or device-ID API tracking, which has its own consent and accuracy issues).
Specific implementations
| Tool | Identifier | Salt/cookie lifetime | Returning visitor next day = same unique? |
|---|---|---|---|
| Plausible | SipHash(IP + UA + domain + daily salt) | Salt rotates 00:00 UTC daily | No (new unique) |
| Matomo (cookieless) | SHA1 hash of similar fingerprint | Rotates daily by default | No |
| Matomo (with cookies) | Cookie ID (_pk_id) |
13 months default | Yes |
| Umami | SHA256(IP + UA + domain + salt) | Configurable, default rotates daily | No |
| PostHog (anonymous) | distinct_id cookie or in-memory |
1 year cookie default | Yes (if cookied) |
| GA4 | Client ID cookie (_ga) |
2 years | Yes |
If you migrated from Universal Analytics or GA4 to Plausible/Umami/cookieless Matomo and your “unique visitors” number went up, this is why. You’re now counting daily-uniques, which is a larger universe than monthly-uniques.
Engaged sessions (the GA4 invention)
Engaged sessions are a GA4 concept that doesn’t natively exist in the self-hosted tools. The definition: a session that lasted 10+ seconds, OR had a conversion, OR had 2+ pageviews. (The 10-second threshold is configurable in GA4 admin between 10s and 60s.)
Why this matters for migration: if your team is used to GA4’s “engaged sessions” tile and you move to Plausible, the equivalent concept simply doesn’t exist as a default report. You can approximate it:
- Plausible: filter your analytics for sessions with 2+ pageviews (a proxy). Time-on-page filtering requires custom event setup.
- Matomo: “visits with conversion” + “visits with 2+ pageviews” via segments. Time-based filtering requires custom dimensions or Goals.
- Umami: filter by
sessions where views > 1in custom reports. - PostHog: trivially — the event-stream model means you can query any combination of conditions.
The honest take: “engaged sessions” was Google’s response to bounce rate being too negative for ad-supported content sites. It’s not a more accurate metric — it’s a more flattering one. Whether you adopt it in your self-hosted stack is a stakeholder-communication question, not a measurement-accuracy question.
What this means for migration
Three migration patterns we see, and the metric shifts to expect:
Universal Analytics → Plausible
- Bounce rate: +15 to +25 points (UA used the “single hit” definition Plausible inherits, but UA had more aggressive bot filtering so reported lower)
- Time on page: -20 to -50% (UA’s “interaction events” inflated avg time-on-page; Plausible doesn’t have them)
- Unique visitors: +30 to +60% (UA used cookie ID; Plausible uses daily salt rotation)
- Sessions: ~unchanged (same 30-min idle window)
GA4 → Plausible
- Bounce rate: +30 to +45 points (GA4’s engagement-based definition is fundamentally different)
- Time on page: varies wildly — GA4 measures session-engagement, not page-time
- Unique visitors: +20 to +50% (GA4 cookie ID vs Plausible daily salt)
- “Engaged sessions” → doesn’t exist in Plausible by default
GA4 → Matomo (with cookies enabled)
- Bounce rate: +10 to +20 points (closer to Universal Analytics-era definitions)
- Unique visitors: ~unchanged (both use cookies, both have ~1-year lifetimes)
- Time on page: similar with heartbeat enabled
If you’re presenting a year-over-year comparison after a migration, you need the metric-shift table above as a caveat — otherwise the “GA4 said 35%, Plausible says 67%” gets read as “our content quality halved” when nothing changed.
When NOT to obsess over these definitions
Three situations where the definitions matter less than you think:
- You’re tracking trends, not absolute values. If you’ve been on Plausible for a year and your bounce rate moved from 70% to 73%, that’s a real signal. The absolute number 73% only matters if you’re comparing to a benchmark calculated the same way.
- You only care about conversions and revenue. Engagement metrics are derivative. If a campaign drives bounces but the bouncers are buying, the campaign is working. Goal/conversion tracking in any of the four tools is well-defined and consistent.
- You’re presenting to a non-technical stakeholder. Don’t open the meeting with “bounce rate methodology in Plausible”. Show one number, the trend, and the business outcome. The methodology footnote belongs in the appendix.
Pick the right metric for the question
| Question | Best metric | Why |
|---|---|---|
| Is my campaign driving quality traffic? | Conversion rate | Tool-independent, consistent |
| Are people engaging with my content? | Scroll depth + 2+ pageview sessions | More reliable than time-on-page |
| How many distinct people came this month? | Use Matomo with cookies if accurate count matters | Cookieless gives daily-uniques, not monthly-uniques |
| Is content X performing better than content Y? | Compare time-on-page within same tool | Trend signal works; absolute number doesn’t |
| Did the redesign improve engagement? | Bounce-rate delta vs control | Same definition, same tool, before/after |
FAQ
Why is my bounce rate higher in Plausible than GA4?
Different definitions. Plausible uses the classic “1 pageview = bounce” rule. GA4 uses “1 – engagement rate” where engagement requires 10+ seconds OR a conversion OR 2+ pageviews. The 10-second threshold is the main difference — most blog readers who spend a few seconds on a single page are bounces in Plausible but engaged sessions in GA4.
How do I get accurate time-on-page in Matomo?
Enable heartbeat tracking by calling _paq.push(['enableHeartBeatTimer', 15]) in your Matomo snippet (15 = seconds between heartbeats). This sends a “still active” ping every 15 seconds while the user has the page focused. It adds traffic to your Matomo server — budget for ~2-4x the event volume — but gives you accurate time-on-page even for the last pageview in a session.
Why does Plausible say I have 50K unique visitors when GA4 said 30K?
Plausible counts unique-device-days (because the salt rotates daily, a returning visitor counts as a new unique each day). GA4 counts unique-cookie-IDs over the reporting period (a returning visitor is the same unique for 2 years by default). The 50K number is “unique device sessions per day, summed over the month”. The 30K number is “distinct cookies seen in the month”. Both are valid; they measure different universes.
What is an engaged session?
A GA4-specific concept: a session that lasted 10+ seconds, had a conversion, or had 2+ pageviews. Self-hosted tools (Plausible, Matomo, Umami) don’t have this metric by default — you’d compute it via filters or custom reports. The threshold is configurable in GA4 admin between 10 and 60 seconds.
Can I make Plausible’s bounce rate match GA4’s?
Not by default. Plausible doesn’t support time-thresholded bounce rate. If you need parity for stakeholder reporting, the workaround is to send custom events for “engagement signals” (scroll past 50%, time-on-page > 10s via a JS timer) and define a “bounce” in your dashboard as “no engagement event fired”. This is a custom analysis, not a built-in metric.
Which tool has the most accurate metrics?
None of them are “accurate” in an absolute sense — they’re all approximations of user behaviour based on what the browser will tell them. PostHog has the richest data model (event stream + session replay) so it gives you the most flexibility. Matomo with all options enabled (heartbeat, cookies, full ecommerce) gives you the most GA4-comparable numbers. Plausible and Umami are the simplest and most opinionated — pick them if you want consistency over flexibility.
What to do next
- If you’re picking a self-hosted tool right now: use our /picker/ — it asks about your stakeholder needs and metric requirements, and points to a tool that won’t surprise you.
- If you’ve migrated from GA4 and metrics shifted: publish a one-pager internally with the shift table from this article. Stakeholders need to know that 70% Plausible bounce ≠ 70% GA4 bounce.
- If you’re running Matomo and want accurate time-on-page: enable heartbeat tracking. The Matomo on Hetzner guide covers the deploy; the heartbeat setting goes in your tracker snippet.
- If you’re building dashboards and need consistency: stick to conversions, scroll-depth events, and within-tool trend lines. Engagement metrics across tools are unreliable and will hurt your team’s trust in the data.
Found this useful?
Try the Stack Picker to get a personal recommendation, or browse the install recipe library.