Proof
    Impress Blinds — cost per enquiry down 62.63%, $23.6 to $8.82SLS Solicitors — cost per enquiry down 58.48%, $84.64 to $35.14FixCare Property — cost per enquiry down 56.36%, $35.24 to $15.38Rubbish Removal WA — cost per enquiry down 53.18%, $71.02 to $33.25Floral Cakery — cost per enquiry down 49.82%, $13.83 to $6.94ILLUMINATE Laser Emporium — cost per enquiry down 48.54%, $138.65 to $71.35Aussie Plumbing — cost per enquiry down 41.96%, $117.75 to $68.34Sydney Fence Painting — cost per enquiry down 33.68%, $136.62 to $90.61Alliance Plumbing — cost per enquiry down 29.6%, $81.26 to $57.21Gridless Build Solutions — cost per enquiry down 29.16%, $78.16 to $55.37FacilityWorx — cost per enquiry down 23.62%, $157.13 to $120.01Cornerstone Roofing — cost per enquiry down 20.47%, $41.71 to $33.17A council finance platform — 194 of 194 requirements metA council finance build — 14 weeks to UAT, −34% 10-yr costA cultural institution — $634K of $750K kept workingA council platform — $470,106 built vs $503,262 SaaSA federal agency — n=5,000 prevalence survey at ±1.4%A civic mural — 36 concepts for a 71m × 9m wallA regional shire — 32-page visitor guide, 3 weeks earlyA shire council — one platform retiring 8 of 9 vendorsA pressure washing business — 138 jobs at A$20.43 eachA pressure washing business — 21.20% conversion rateA carpet cleaner — 53 jobs in 15 days at A$24.92 eachA roofing company — 68 quote requests in 35 daysA CCTV installer — 39 qualified leads in 15 daysA fence painter — 36 jobs in 24 days, quotes by day 3A maintenance business — live in 8 weeks, 3 stacks gone41 numbered clauses, published in full5.0 across every Google review$120M+ in media under management250+ active engagements across five countries
    Small Business

    Google Ads Conversion Tracking: Fix Duplicate Sales & GST

    Your Google Ads ROAS looks great — but if your purchase tag fires every time someone lands on your thank-you page, that number is a lie. Worse, the inflated revenue total is probably feeding Xero or MYOB without stripping GST, meaning your next BAS could be wrong before you've touched a single figure. This guide shows you exactly where the break is, how to fix it with one line of JavaScript, and how to confirm the corrected number matches your Xero sales report before the ATO comes knocking.

    • 21 December 2024
    • 13 min read
    • 2,869 words
    • 4 sources

    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.

    A GTM Preview Mode panel showing the same purchase tag firing twice under the 'Tags Fired' column, both against the same order confirmation
    A GTM Preview Mode panel showing the same purchase tag firing twice under the 'Tags Fired' column, both against the same order confirmation URL — one on initial load and one on page refresh

    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
    Flow diagram — showing order total path from checkout platform → GTM dataLayer → <a href="/paid-advertising/google-ads" class="internal-link">Google
    Flow diagram — showing order total path from checkout platform → GTM dataLayer → <a href="/paid-advertising/google-ads" class="internal-link">Google Ads</a> conversion

    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

    1. Open your GTM container. Click Preview in the top-right corner. A new tab will open and attach the GTM debugger to your site.
    2. 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.
    3. 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.
    4. 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.
    5. Click on the Tags Fired entry for your purchase tag. Find the variable feeding the value field. If it references a dataLayer key like ecommerce.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

    1. In your GTM container, go to Variables → New → Variable Configuration → Custom JavaScript.
    2. Name it something unambiguous: CJS - Revenue GST Exclusive.
    3. 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

    1. Open your purchase tag configuration in GTM.
    2. Find the field labelled Value (in a Google Ads conversion tag) or Value under the ecommerce event parameters (in a GA4 event tag).
    3. Replace the existing variable reference with {{CJS - Revenue GST Exclusive}}.
    4. Save the tag. Do not publish yet — you still need the deduplication fix.
    GTM tag configuration screen showing the Value field of a Google Ads conversion tag, with the variable picker open and 'CJS - Revenue GST Ex
    GTM tag configuration screen showing the Value field of a Google Ads conversion tag, with the variable picker open and 'CJS - Revenue GST Exclusive' selected from the variable list

    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.

    1. 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]: '';
    }
    1. Create a second Custom JavaScript variable named CJS - Current Order ID that reads the transaction ID from your dataLayer:
    function() { return {{dlv - ecommerce.purchase.actionField.id}} || '';
    }
    1. 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}}.
    2. 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.

    Decision tree — showing the tag fire logic: 'Purchase event fires in dataLayer' → 'Read last_order_id cookie' → if match: 'Block tag, do not
    Decision tree — showing the tag fire logic: 'Purchase event fires in dataLayer' → 'Read last_order_id cookie' → if match: 'Block tag, do nothing' / if no match:

    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

    1. In Xero, go to Accounting → Reports → Sales Detail.
    2. 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).
    3. 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

    1. In Google Ads, go to Tools → Measurement → Conversions.
    2. Click on your purchase conversion action. Select View in report.
    3. 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:

    1. "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.
    2. "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

    1. Customer completes payment. Your platform (WooCommerce, Shopify, or your booking system) processes the order and marks it as confirmed.
    2. The platform fires a dataLayer.push with a purchase event, 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.
    3. GTM listens for the purchase custom event. The deduplication check runs: is this transaction ID already in the last_order_id cookie? No — it fires the purchase tag.
    4. The purchase tag sends the GST-exclusive revenue value (via the CJS - Revenue GST Exclusive variable) to Google Ads and GA4. The last_order_id cookie is set with a 30-minute expiry.
    5. 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.
    End-to-end flow diagram — showing: 'Customer pays' → 'Platform fires dataLayer purchase event (once, GST-exclusive value)' → 'GTM deduplicat
    End-to-end flow diagram — showing: 'Customer pays' → 'Platform fires dataLayer purchase event (once, GST-exclusive value)' → 'GTM deduplication check passes' →

    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.

    What to do next

    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.
    Talk to SoudCoh

    Where the claims in this piece come from.

    Listed so you can check the reasoning rather than take it on trust. If a source has moved or been superseded, tell us and we will correct the piece.

    1. Google Ads Help: Use a transaction ID to minimise duplicate conversions — https://support.google.com/google-ads/answer/6386790

    2. Google Tag Platform: The data layer — https://developers.google.com/tag-platform/tag-manager/datalayer

    3. Australian Taxation Office: GST calculation and reporting guidance — https://www.ato.gov.au/businesses-and-organisations/gst-excise-and-indirect-taxes/gst

    4. Google Tag Manager Help: Preview and debug containers — https://support.google.com/tagmanager/answer/6107056

    Read next

    Filed under the same desk first. The full index is searchable and filters by reader.

    If you would rather we just did it.

    The briefing above is the reasoning. These are the pages that describe what it looks like as a piece of paid work, including what it costs and what gets reported.

    Apply it to your account

    Reading it is the easy half. Thirty minutes with someone who runs accounts and you leave with a written list of what is leaking on yours — yours to keep either way.

    No pitch deck. No upsell. A real conversation and a written list of leaks.