Server-vs-client reconciliation
Customers regularly compare their Zenovay numbers against GA4, Stripe receipts, or their own backend logs and find that the numbers don't quite line up. Sometimes Zenovay reports more events. Sometimes fewer. The honest answer is that every analytics tool — Zenovay included — is making an estimate based on what reached the browser, what reached the server, and what was confirmed by a webhook.
Reconciliation is Zenovay's mechanism for quantifying that gap with confidence labels instead of pretending it doesn't exist.
What it compares
Reconciliation runs as a daily job that compares three layers of event truth, per website, per metric type, per day:
| Layer | What it measures | Where it lives |
|---|---|---|
| Client | Events fired by the browser tracker | page_views, user_events with source = 'browser' |
| Server | Events ingested via POST /api/v1/events | Same tables, source = 'server' |
| Webhook | Stripe / LemonSqueezy / Polar webhook confirmations | payment_events with status = 'succeeded' |
For each (website, day, metric) triple it records: how many events each layer saw, how many matched, the estimated loss percentage, and a primary mismatch reason ranked from a fixed set.
The three V1 metric types
V1 covers the three metrics that have an unambiguous truth layer:
- Pageviews — client (browser) vs server (
POST /api/v1/events). - Goal completions — client goal events vs server-side goal events.
- Revenue events — client purchase events vs Stripe webhook truth (
payment_events).
Custom events, identify calls, and other event types are out of scope for V1 — they don't have a comparable truth layer.
How loss is estimated
For pageviews and goals, the truth layer is server_count. For revenue, the truth layer is webhook_count (the payment provider's webhook is the source of truth — that's what actually charged the customer).
estimatedLoss = (truth - client) / truth × 100
Negative values are allowed: if the server captured more events than the browser tracker did, that's information too — it means your tracker missed events that server-side ingestion caught, which is the entire point of running both.
Confidence labels
Every reconciliation row carries one of three labels:
| Label | When it applies |
|---|---|
| High confidence | Both layers each have ≥100 events and the estimated loss is below 30%. |
| Medium confidence | Either layer is in the 50–100 event range, or the loss falls between 30% and 60%. |
| Limited data | Below 50 events on either layer, the loss exceeds 60%, or only one layer is present at all. |
"Limited data" is not a problem to fix — it just means the sample is too small or one-sided to draw a confident conclusion. Confidence labels keep the Trust view honest about what it knows.
Mismatch reasons
When reconciliation detects a meaningful gap it picks one of seven reasons, ranked by detection priority:
| Reason | Detection rule | Actionable? |
|---|---|---|
webhook_delay | Client > webhook by ≥20% AND today's webhook count is ≥2σ below the trailing 6-day mean | ✓ |
client_blocked | Server > client by ≥10% | ✓ |
route_mapping | Top 1–2 routes account for ≥50% of the gap | ✓ |
duplicate_suppression | The ingest pipeline deduplicated browser events within the day | informational |
id_stitching | High variance in unmatched-client across visitor segments | informational |
no_server_layer | server_count = 0 and client_count > 0 | informational |
unknown | No threshold tripped | informational |
Three reasons are actionable — meaning the action belongs to you (fix your CSP, configure SDK retry, check Stripe webhook delivery). The other four are diagnostic context.
Where to see it
Open any per-domain dashboard in app.zenovay.com and select the Trust tab. You'll see:
- The estimated tracking loss for the trailing 7 days, with a confidence label.
- A per-metric breakdown (pageviews, goals, revenue) showing client / server / matched counts and the gap visualised as a striped section of a completeness bar.
- The top mismatch reasons ranked by event impact, with action steps for the actionable ones.
- (Pro and above) A per-route drilldown showing exactly which routes contribute most to the gap.
What it is not
- It is not a guarantee of correctness. It's a measurement of measurement quality.
- It is not an attempt to fix the gap. The fixes — CSP configuration, webhook configuration, route normalisation — belong on your side.
- It does not name third-party services or vendors that you aren't already using. Mismatch reasons stay generic so the action steps remain relevant whatever your stack looks like.
- It does not create new data collection. Reconciliation reads existing
page_views,user_events, andpayment_eventsrows — there are no new cookies, no new identifiers, and the cookieless tracking guarantee is unchanged.
Plan availability
The Trust tab itself is available on every plan. Per-route drilldown and the action steps for actionable reasons are available on Pro and above. Free shows aggregates and reason names without the route-level detail.
Related concepts
- Bounce rate — another quality signal that benefits from reconciliation context.
- Server-side event ingestion — the
POST /api/v1/eventsendpoint that powers the server layer of reconciliation. - Cookieless tracking — why the in-memory tracker doesn't pollute reconciliation with stale identifiers.