Skip to main content
5 min read

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:

LayerWhat it measuresWhere it lives
ClientEvents fired by the browser trackerpage_views, user_events with source = 'browser'
ServerEvents ingested via POST /api/v1/eventsSame tables, source = 'server'
WebhookStripe / LemonSqueezy / Polar webhook confirmationspayment_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:

  1. Pageviews — client (browser) vs server (POST /api/v1/events).
  2. Goal completions — client goal events vs server-side goal events.
  3. 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:

LabelWhen it applies
High confidenceBoth layers each have ≥100 events and the estimated loss is below 30%.
Medium confidenceEither layer is in the 50–100 event range, or the loss falls between 30% and 60%.
Limited dataBelow 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:

ReasonDetection ruleActionable?
webhook_delayClient > webhook by ≥20% AND today's webhook count is ≥2σ below the trailing 6-day mean
client_blockedServer > client by ≥10%
route_mappingTop 1–2 routes account for ≥50% of the gap
duplicate_suppressionThe ingest pipeline deduplicated browser events within the dayinformational
id_stitchingHigh variance in unmatched-client across visitor segmentsinformational
no_server_layerserver_count = 0 and client_count > 0informational
unknownNo threshold trippedinformational

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, and payment_events rows — 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.

  • Bounce rate — another quality signal that benefits from reconciliation context.
  • Server-side event ingestion — the POST /api/v1/events endpoint that powers the server layer of reconciliation.
  • Cookieless tracking — why the in-memory tracker doesn't pollute reconciliation with stale identifiers.
Was this page helpful?