Why your thank-you page is double-counting every sale
A standard tag manager setup puts a purchase tag on the order confirmation URL and sets the trigger to Page View. That feels logical — the page only loads when someone buys, right? Wrong. The confirmation URL stays in the browser after the purchase. The customer refreshes. They hit the back button and click forward again. They paste the link to their partner to show them what arrived. Every single one of those actions loads the page, fires the tag, and sends a full revenue value to Google Ads.
How a page-view trigger multiplies your conversion count
Each extra fire is not flagged as a duplicate. Google Ads records it as a new conversion. Your conversion count climbs. Your reported revenue climbs with it. Not one extra dollar has changed hands. The campaign looks like it is printing money when it is printing fiction.
According to the Google Tag Manager dataLayer documentation, the value field in a purchase event is set entirely by the implementer. GTM does not deduplicate or validate that value for you. If you push a revenue figure on every page load, GTM will report every page load as a sale. That is not a bug — it is exactly what you told it to do.
What the inflated number does to your decisions
A trades business spending $3,000 a month on Google Ads and seeing a reported ROAS of 8× is almost certainly not running an 8× campaign. They are running a 4× campaign with a purchase tag that fires twice per order on average. The difference is not academic — it changes whether you scale spend, cut a keyword, or fire the agency.
The GST problem nobody mentions when they set up your tag
Double-firing is the visible problem. The GST problem is quieter and, for an Australian business, more dangerous. Most GTM implementations pull the order total straight from the checkout platform. That total is the figure shown to the customer at the bottom of their receipt — the GST-inclusive price. Your customer paid $110. Your tag is reporting $110. But $10 of that was never yours. It belongs to the ATO.
What the ATO actually requires
The ATO's GST record-keeping requirements at ato.gov.au/business/gst/keeping-gst-records specify that tax invoices must record GST-exclusive and GST-inclusive amounts separately. Your analytics revenue variable needs to match the GST-exclusive figure that sits in your accounting system — not the checkout total the customer saw.
Your ROAS is not high — your revenue variable is including GST that was never yours to keep.
How the mismatch reaches your BAS
When the inflated, tax-inclusive number flows into a Xero webhook or a manual CSV export, Xero's BAS auto-fill reads it as your actual sales figure for the period. Xero's own reconciliation guidance at central.xero.com confirms that imported revenue figures are expected to be GST-exclusive for correct BAS auto-fill. If your GTM revenue is GST-inclusive, the GST line in your BAS understates your actual liability. That is not a rounding error. It is a compliance risk that can trigger an ATO review.
To make the scale of the problem concrete:
| Scenario | GTM reported revenue | Actual GST-exclusive revenue | GST understated per quarter |
|---|---|---|---|
| $50,000/qtr in sales (GST-inclusive fed to GTM, single fire) | $50,000 | $45,455 | $4,545 |
| $50,000/qtr in sales (GST-inclusive, average 1.5× double-fire) | $75,000 | $45,455 | $4,545 + inflated ROAS distortion |
| $50,000/qtr in sales (GST-exclusive, single fire — correct) | $45,455 | $45,455 | $0 |
How to audit your current purchase tag in under five minutes
You do not need agency access or a developer to check this. You need GTM preview mode and your own order confirmation URL.
Step-by-step audit process
- Open your GTM container. Click Preview in the top-right corner. A new tab will open and attach the GTM debugger to your site.
- Navigate to your order confirmation URL — either place a real test order or use a saved confirmation URL from a recent transaction. The GTM debug panel will appear at the bottom of the page.
- In the debug panel, click on the Tags tab and look at the Tags Fired column. If you see your purchase tag listed as firing on Window Loaded, DOM Ready, or All Pages — Page View rather than on a custom dataLayer event, you have the problem described in this article.
- Now refresh the confirmation page. Watch the Tags Fired list. If your purchase tag fires a second time on the same page, you have definitive proof of double-counting.
- Click on the Tags Fired entry for your purchase tag. Find the variable feeding the
valuefield. If it references a dataLayer key likeecommerce.purchase.actionField.revenue, check what that key actually contains — click the Variables tab and find the value. If it matches the GST-inclusive total your customer paid (e.g. $110 rather than $100), the tax strip is missing.
What a passing audit looks like
A correctly configured tag manager setup shows the purchase tag firing exactly once, triggered by a custom event (not a page view), with the value field containing a GST-exclusive figure. If all three of those are true, you can stop reading here. If any one of them fails, keep going.
Your ROAS is not high — your revenue variable is including GST that was never yours to keep.
The one-line JavaScript variable that strips GST from your revenue
This is the fix. It takes less time to implement than it took to read the last two sections.
Creating the Custom JavaScript variable in GTM
- In your GTM container, go to Variables → New → Variable Configuration → Custom JavaScript.
- Name it something unambiguous:
CJS - Revenue GST Exclusive. - Paste the following code exactly:
function() { var rawRevenue = {{dlv - ecommerce.purchase.actionField.revenue}}; if (!rawRevenue) return 0; return parseFloat((rawRevenue / 1.1).toFixed(2));
} Swap {{dlv - ecommerce.purchase.actionField.revenue}} for whatever dataLayer variable name your platform uses. WooCommerce typically uses {{dlv - ecommerce.purchase.revenue}}. Shopify via a third-party GTM integration often uses {{dlv - ecommerce.value}}. Check your own dataLayer output in preview mode to confirm the exact key name.
The ATO wants GST-exclusive figures; so does Xero. GTM doesn't care — that's your job.
Why dividing by 1.1 is the correct Australian GST calculation
Australian GST is 10%. A GST-inclusive price of $110 contains $10 GST and $100 of actual revenue. To recover the GST-exclusive amount, divide by 1.1 — not subtract 10%. Subtracting 10% of $110 gives $99, which is wrong. Dividing $110 by 1.1 gives $100, which is correct. This is the calculation the ATO uses, and it is what Xero expects when it auto-fills your BAS.
Wiring the new variable into your purchase tag
- Open your purchase tag configuration in GTM.
- Find the field labelled Value (in a Google Ads conversion tag) or Value under the ecommerce event parameters (in a GA4 event tag).
- Replace the existing variable reference with
{{CJS - Revenue GST Exclusive}}. - Save the tag. Do not publish yet — you still need the deduplication fix.
Stopping the double-fire requires a transaction ID deduplication check
Stripping GST fixes the tax accuracy problem. It does not stop the tag firing twice. You need both fixes published together or you are still reporting a single correct-value sale twice — which still doubles your reported ROAS.
How the deduplication cookie works
The logic is simple: before the purchase tag fires, check whether this order ID has been seen before. If it has, block the tag. If it has not, allow the tag and record the order ID so the next check will block it.
- Create a new GTM variable of type Custom JavaScript. Name it
CJS - Last Order ID Cookie. Use this code to read the cookie:
function() { var match = document.cookie.match(/last_order_id=([^;]+)/); return match ? match[1]: '';
} - Create a second Custom JavaScript variable named
CJS - Current Order IDthat reads the transaction ID from your dataLayer:
function() { return {{dlv - ecommerce.purchase.actionField.id}} || '';
} - On your purchase tag, add a trigger exception: fire the tag only when
{{CJS - Current Order ID}}does not equal{{CJS - Last Order ID Cookie}}. - Create a Tag of type Custom HTML — name it
CHT - Set Order ID Cookie— that fires on the same custom purchase event, sequenced to fire after the purchase tag:
<script> var orderId = {{CJS - Current Order ID}}; if (orderId) { var expiry = new Date(Date.now() + 30 * 60 * 1000).toUTCString(); document.cookie = 'last_order_id=' + orderId + '; expires=' + expiry + '; path=/; SameSite=Lax'; }
</script> Why 30 minutes is the right cookie expiry
Thirty minutes covers any reasonable session in which a customer might refresh or revisit the confirmation page. It is short enough that a different order placed by the same customer hours later will not be accidentally blocked. Adjust to 60 minutes if your checkout flow has a longer typical session, but do not go longer than 24 hours or you risk blocking legitimate repeat purchases on shared devices.
How to confirm your fix matches Xero before the next BAS is due
Publishing the fix is not the end of the job. You need to verify that the corrected GTM revenue and your Xero GST-exclusive sales total are telling the same story. If they are not within 2% of each other, something is still wrong.
Pulling the right numbers from Xero
- In Xero, go to Accounting → Reports → Sales Detail.
- Set the date range to the current GST quarter (1 January–31 March, 1 April–30 June, 1 July–30 September, or 1 October–31 December AEST).
- Export to CSV. Open the file and sum the Subtotal column — this is the GST-exclusive revenue figure. Do not use the Total column, which includes GST.
Pulling the right numbers from Google Ads
- In Google Ads, go to Tools → Measurement → Conversions.
- Click on your purchase conversion action. Select View in report.
- Set the date range to match the Xero export. Column: Conv. value. This is the total revenue Google Ads has recorded for the period.
Interpreting the comparison
| Variance between GTM conv. value and Xero GST-exclusive subtotal | What it means | Action |
|---|---|---|
| Under 2% | Normal rounding across multiple transactions | No action required — reconciliation passes |
| 2%–5% | Possible partial double-fire or minor schema mismatch | Re-run preview mode audit on a test order |
| Over 5% | Tag still misfiring or GST strip not applied correctly | Re-check dataLayer variable name and cookie logic |
| GTM value is exactly 10% higher than Xero | GST strip variable is not wired into the tag | Confirm the CJS variable is saved and the tag references it |
| GTM value is 2× or more of Xero | Double-fire is still happening and GST is not stripped | Both fixes need to be re-published |
Xero's reconciliation guidance confirms that imported revenue figures should be GST-exclusive for correct BAS auto-fill. A match between your corrected GTM revenue and Xero's GST-exclusive subtotal column is the specific test that tells you both your ROAS reporting and your BAS are now accurate.
The ATO wants GST-exclusive figures; so does Xero. GTM doesn't care — that's your job.
Why most agencies never fix this — and what to ask before you hire one
This is not a technical problem that requires rare expertise. The fix described in this article takes 20 minutes for someone who knows what they are looking for. The reason most small-business tag manager setups never get this fix is simpler than that: agencies bill for installs, not audits.
The install-and-leave incentive problem
An install is complete the moment a purchase event appears in GTM's debug panel. The tag fires. The client sees conversions in Google Ads. Everyone is happy — until the next BAS. There is no invoice line for 'checked that the revenue variable strips GST and reconciles with your Xero sales report.' There is no client request for it either, because most clients do not know to ask.
Per the ATO's GST record-keeping requirements, the obligation to maintain accurate GST records sits with the business owner, not the agency. That means the compliance risk lands on you regardless of who built the tag.
What SoudCoh does differently
SoudCoh includes a GST reconciliation check as a mandatory step in every tag manager setup engagement. Before the GTM container is published, the corrected GA4 revenue is compared against the client's Xero GST-exclusive sales total. A screenshot of both figures — side by side, same date range — is provided to the client as documented proof. That screenshot is part of the deliverable, not a bonus. See examples of this in practice on the SoudCoh case studies page.
Two questions to ask any agency before you sign
Before engaging anyone for conversion tracking work, ask these two questions directly:
- "Will you show me a side-by-side of GTM revenue versus my Xero GST-exclusive total?" — A yes means they understand Australian compliance requirements. A blank stare means they do not.
- "How do you prevent purchase tag double-fires on page refresh?" — The correct answer involves either a custom event trigger or a deduplication mechanism. 'We use a thank-you page URL trigger' is the wrong answer.
If the agency hesitates on either question, the audit has never been done. That is not speculation — it is the default state of most small-business GTM containers we review at SoudCoh.
What a correct GTM purchase setup looks like end-to-end
Once both fixes are in place and the Xero reconciliation passes, your entire reporting stack will be internally consistent. Here is what that correct state looks like, from the moment a customer clicks the pay button to the moment your BAS auto-fills.
The correct event trigger chain
- Customer completes payment. Your platform (WooCommerce, Shopify, or your booking system) processes the order and marks it as confirmed.
- The platform fires a
dataLayer.pushwith apurchaseevent, the transaction ID, and the GST-exclusive revenue value — or your GTM tag receives a server-side webhook from the platform at this moment. The event fires exactly once, triggered by the order confirmation, not by a page load. - GTM listens for the
purchasecustom event. The deduplication check runs: is this transaction ID already in thelast_order_idcookie? No — it fires the purchase tag. - The purchase tag sends the GST-exclusive revenue value (via the
CJS - Revenue GST Exclusivevariable) to Google Ads and GA4. Thelast_order_idcookie is set with a 30-minute expiry. - If the customer refreshes the confirmation page, the deduplication check finds a matching cookie and blocks the tag. Zero additional conversions are recorded.
What you can do with accurate numbers
When your tag manager setup is correct, the downstream effects are immediate and practical:
- Your Google Ads ROAS reflects actual GST-exclusive revenue — a number that matches what your accountant is working with.
- Your GA4 revenue report can be shared with a bookkeeper or CFO without a disclaimer about GST adjustments.
- Your BAS auto-fill in Xero or MYOB reconciles on the first attempt, saving 20–40 minutes per quarter of manual correction.
- If the ATO requests substantiation of your GST liability, your analytics data and your accounting records tell the same story from the same source.
A correct tag manager setup is not an advanced configuration. It is the baseline. The steps in this article are a one-time 20-minute fix that protects your ad spend decisions and your BAS every quarter from this point forward. The cost of not doing it is a ROAS number you cannot trust and a GST liability you may be understating.
What to do next: Run the five-minute GTM preview audit described above right now — before you close this tab. Check whether your purchase tag fires twice on a page refresh, and check whether the value field contains a GST-inclusive figure. If either check fails, the fix is in this article and takes 20 minutes to implement. If you would rather have someone confirm the work is done correctly and reconciled against your actual Xero data, book a free 20-minute GTM audit with SoudCoh at soudcoh.com/paid-advertising/google-ads and we'll check your purchase tag revenue against your Xero GST-exclusive total before your next BAS is due.

