Skip to content

Overview

Controlla Voice is an AI-powered voice and audio SaaS platform selling subscription plans and one-time credit packs. Customers pick Basic, Plus or Creator, billed monthly or annually, with every payment processed through Stripe.

Founder Frederik Bonneau ran marketing and analytics in-house, bringing in freelancers as needed. GA4 had been implemented internally using client-side Google Tag Manager alongside a server-side container hosted on Stape, with Meta Pixel, Cometly, Reddit Pixel and Usercentrics all in the mix.

The team knew GA4 revenue did not match Stripe. Several freelancers had tried to fix it. None of them found the cause, because the setup contained two separate errors that were quietly cancelling each other out.

At a Glance

 

The Challenge: Two Errors That Were Hiding Each Other

The client's confidence in GA4 was low and they knew why in broad terms — the revenue number was wrong. What nobody could work out was the cause, and the reason it had survived several attempts is the most instructive part of this story.

GA4 was over-reporting revenue through duplicate purchases. It was simultaneously under-reporting revenue by missing every subscription renewal. The two errors partly offset each other, so the headline mismatch looked like a single moderate discrepancy rather than two large opposing ones. Every previous investigation chased one number and found it roughly plausible.

The GAfix audit surfaced nine issues. These were the four that mattered most.

PROBLEM 1:  Every purchase was counted roughly twice

The transaction ID was generated on the frontend, and it changed whenever the page re-rendered. GA4 saw each new ID as a new transaction, so a single payment could land as two. Google Ads conversions compounded it — they were being sent from both the client-side and the server-side container.

PROBLEM 2:  Subscription renewals never reached GA4 at all

Stripe processes a subscription renewal without any browser interaction. No page loads, no tag fires, nothing reaches GA4. For a business whose model is recurring revenue, an entire revenue stream was invisible — and invisible in a way that no amount of checking the website could reveal.

PROBLEM 3:  Two measurement IDs, and server-side only forwarded purchases

Tracking was split across two GA4 measurement IDs. The server-side container forwarded purchase events and dropped checkout and lead events entirely, which is why begin_checkout never appeared in GA4 despite being one of the three events the team relied on.

PROBLEM 4:  sign_up fired on every page refresh

The sign_up event fired whenever anyone visited or refreshed /app/welcome, so registrations were inflated by repeat views. Logins were not tracked at all, and no User-ID was being set, which meant customers could not be connected across devices or matched to their Stripe payment records.

Four more issues sat underneath these: add_to_cart firing multiple times from three legacy triggers, Cometly tracking running on localhost, sandbox and admin pages, and server-side Meta events missing browser information and event source URL.

What the Bad Data Was Paying For

Google Ads was spending roughly $7,000 a month on Target ROAS and Maximize Conversion Value bidding. Both strategies optimise against the conversion value GA4 reports.

Duplicate purchases inflated that value. Missing renewals removed it. Smart Bidding was therefore allocating real budget against a number that was wrong in two directions at once — and channels that attracted long-term subscribers were systematically undervalued, because the renewal payments those customers went on to make were never measured.

The GAfix Audit: 2 Minutes to Find What Months of Freelancing Missed

The automated audit ran in about two minutes. Performing the same checks by hand was estimated at one to two days of analyst time.

It returned a GA4 health score of 54/100 — data collection was active, but revenue reporting could not be trusted. Because the fixes spanned website code, Stripe, the client-side container and the server-side container, the client chose a managed engagement rather than taking the findings away to implement alone. GAfix documented the required developer changes, implemented the GTM work, and ran end-to-end testing before handover.

The Fixes: Three Changes That Made the Numbers Match

Fix 1: Stripe invoice IDs became the transaction ID

The developer now passes the unique Stripe invoice ID into the purchase dataLayer, and GTM maps it to GA4's transaction_id and to Google Ads' orderId. Because the invoice ID is generated by Stripe rather than the browser, it does not change on re-render.

Google Ads conversion tracking was consolidated into the server-side container at the same time, so the same conversion can no longer be counted by both a browser tag and a server tag.

Result:  Duplicate purchase reporting resolved, and Google Ads started receiving conversion values it could bid on.

Fix 2: Subscription renewals now flow into GA4 via webhook

A Stripe invoice.payment_succeeded webhook sends payment data to the server-side container, where a custom GAfix tag template forwards it to GA4 using the Measurement Protocol.

The implementation checks billing_reason = subscription_cycle to isolate genuine renewals, carries the Stripe invoice ID and the User-ID, reports in USD, and deliberately excludes the initial subscription payment because the website already tracks it.

Result:  Recurring revenue became visible in GA4 for the first time, without double-counting the first payment.

Fix 3: User-ID now follows the customer across the journey

Signup and login dataLayer events were added with the internal user ID, covering email, Google and Apple sign-in. GTM carries that ID across client-side and server-side events and clears it on logout. The same identifier is written into Stripe metadata as controlla_user_id.

Result:  Customer activity can now be connected across devices, sessions and Stripe payments, including renewals.

Alongside those three

  • Tracking consolidated into a single GA4 property
  • UTM and click-ID persistence improved
  • Cometly blocked on internal and test environments
  • User-agent and event source URL added to Meta events
  • customer_type specification defined for new versus returning customers in Google Ads
  • Stape features enabled: Custom Loader, Cookie Keeper, Preview Header, GEO Headers, Bot Detection, Ad Blocker Info

The Results: GA4 Within 1–2% of Stripe

Revenue coverage went from 46% to 96%

A single-day comparison shows the shift clearly.

 

Across the full reporting period, GA4 now matches Stripe within roughly 1–2%, covering both website purchases and recurring payments.

Recurring revenue is measurable for the first time

Subscription renewals — previously absent from GA4 entirely — now appear against the customers who generated them. For a subscription business, this is the difference between measuring acquisition and measuring the business.

Health score improved from 54 to 70

Re-running the audit after implementation returned 70/100, a 16-point improvement.

What the Client Said

Key Takeaways

1. Two tracking errors can hide each other

Over-reporting and under-reporting in the same property produce a mismatch that looks moderate and single-cause. That is why this survived several freelancers: each one checked the total, found it roughly believable, and stopped.

2. Subscription renewals never touch a browser

Any revenue event that happens on your payment provider's servers rather than in a user's browser is invisible to client-side tracking by definition. For subscription businesses this is not an edge case, it is most of the revenue.

3. A transaction ID generated in the browser is not an ID

If it changes on re-render, it cannot deduplicate anything. The identifier has to come from the system that owns the transaction.

4. A falling ROAS can be the strongest evidence the fix landed

When reported performance drops after a tracking fix, check whether the previous number was real before treating it as a regression. In this case the 2.49x never existed.

Run Your GA4 and GTM Audit

The audit that found these nine issues takes about two minutes, runs read-only, and changes nothing in your property. If your revenue numbers do not match your payment processor, it will tell you where to look.

Frequently Asked Questions

Related Posts