AnalyticsAugust 18, 2026 · 8 min

Server-side conversion tracking: recovering the revenue GA4 can't see

On iBoardingPass, the majority of paying customers were missing from conversion reports — overlapping tracking bugs were hiding real revenue from the funnel. The fix was to stop trusting the browser: a server-side Measurement Protocol path, event deduplication, and handling the 3DS redirect flow so conversion became the source of truth instead of the ad-blocker's guess.

Why client-side tracking silently under-counts

Client-side GTM/GA4 fires from the browser, which means it loses events to ad-blockers, privacy modes, tracking-prevention, and — critically for payments — 3DS redirects that navigate the user away from your page before the conversion tag runs. None of this shows up as an error; the numbers are just quietly low. When most of your buyers pay via a 3DS redirect, most of your conversions vanish.

Make the server the source of truth

The payment already happens server-side — the PSP tells your backend when a charge succeeds via webhook. That is the moment a conversion is real. So the backend sends the conversion to GA4 over the Measurement Protocol, using the same client id captured earlier, instead of relying on a browser tag that may never fire.

// On verified PSP webhook -> report conversion server-side
await fetch(`https://www.google-analytics.com/mp/collect?measurement_id=${ID}&api_secret=${SECRET}`, {
  method: 'POST',
  body: JSON.stringify({
    client_id: order.gaClientId,      // captured at checkout start
    events: [{ name: 'purchase', params: {
      transaction_id: order.id,        // dedupe key
      value: order.amountMajor, currency: order.currency,
    }}],
  }),
});

Deduplication: don't double-count the client + server

If the client tag sometimes fires and the server always fires, you can double-count. The fix is a stable transaction_id on both paths so GA4 collapses duplicates into one purchase. The server event is authoritative; the client event, when it survives, just reconciles to the same id.

The 3DS retry flow

3-D Secure adds a redirect and often a retry. The tracking has to survive a user bouncing to the bank's page and back, and it must not count a retried-then-succeeded payment twice. Anchoring the conversion to the server-verified webhook (not the return-to-site page load) handles both: the event fires exactly when the charge is truly settled.

If a large share of your buyers use 3DS or mobile wallets, assume client-side conversion tracking is under-reporting materially. The webhook is the honest signal.

What changed

Moving conversion to a server-side Measurement Protocol path with dedup and 3DS-aware timing recovered visibility into the paying-customer majority that client-side tracking had been dropping — which also unblocked sane PSP-migration planning, because the funnel numbers could finally be trusted.

Key takeaways

  • Client-side conversion tags under-count silently; ad-blockers and 3DS redirects eat events.
  • Fire the conversion from the server on the verified payment webhook.
  • Use a stable transaction_id to deduplicate client and server events.
  • Anchor to settlement, not page load, so retries and redirects don't miscount.

FAQ

What is the GA4 Measurement Protocol used for here?

It lets your server send events to GA4 directly, without a browser tag. For payments that's decisive: the backend reports the purchase the instant the PSP webhook confirms the charge, so conversions no longer depend on a client tag that ad-blockers or 3DS redirects can prevent from firing.

How do you prevent double-counting conversions?

Send a stable transaction_id from both the client and server paths. GA4 collapses events with the same transaction id into one purchase, so an occasional surviving client event reconciles with the authoritative server event instead of adding a duplicate.

AA
Ali Asghar

Senior software engineer & technical lead — 6+ years shipping production multi-tenant SaaS, payments and AI integration in Next.js, Node & TypeScript.

Keep reading
How I built a real-time, cookieless analytics console in Next.jsMulti-currency billing done right: ECB FX sync and cent-accurate invoicing across 18 locales