Google Ads
The GA4 conversion in Google Ads fired on a /thank-you/ page that returned 200 to curl
Run this against your own site:
curl -I https://yoursite.com/thank-you/If it comes back HTTP/1.1 200 OK (no redirect, no token in the URL, no session check), then anyone can load that page. Anyone. A bot. A colleague clicking a link in a CRM note. The person who just submitted the form, hitting refresh. And whatever tag fires on that pageview fires for all of them.
That is the whole mechanism. Everything below is what it looked like in one account, and what it did to the numbers.
What we found
The site was WordPress on a LiteSpeed server. Forms were Contact Form 7. A redirect plugin sent every successful submission to /thank-you/. Google Tag Manager loaded on every page, including that one, and fired a GA4 event on the thank-you pageview. That GA4 event was imported into Google Ads as a conversion action.
Layer | Component | What it did |
|---|---|---|
Form | Contact Form 7 | Collected the enquiry |
Redirect | CF7 redirect plugin | Sent the browser to |
Tag container | GTM (one container ID, no hardcoded tags anywhere else) | Loaded on |
Event | GA4 custom event on thank-you pageview | Fired once per pageview |
Conversion | GA4 event imported into Google Ads | Counted as a conversion |
Counting | "Every" (MANY_PER_CLICK) | One click could produce several conversions |
Each layer is a reasonable choice on its own. Stacked, they produce a conversion that fires whenever a URL loads, with no check on how the URL was reached.
The query-string multiplier
One detail made it worse. The redirect plugin appended every submitted form field to the destination URL:
/thank-you/?your_name=Jane&your_email=jane%40example.com&your_phone=..That's a common plugin behaviour, usually so the thank-you page can personalise itself. But it means every submission produces a unique URL. To GA4, /thank-you/?your_name=Jane and /thank-you/?your_name=Jim are different pages. To a counting rule set to "every", they're different conversions even from the same click if the person goes back and resubmits.
Scenario | Pageviews of | Conversions counted |
|---|---|---|
One submission | 1 | 1 |
Submit, refresh | 2 | 2 |
Submit, back, resubmit with a typo fixed | 2 (different query strings) | 2 |
Submit, bookmark, revisit next day | 2 | 1 per session, or 2 if "every" |
Support agent opens the URL from a ticket | 1 | 1 |
How much it inflated
We compared the GA4 web-form action against a second action on the same form, a Google Ads tag configured to count once per click. Same page, same submissions, different counting rules.
Period | GA4 action | Ads-tag action | Ratio |
|---|---|---|---|
15–31 Aug | 137.4 | 22.0 | 6.25× |
2–4 Sep (after GA4 counting changed) | 31.4 | 14.0 | 2.24× |
The Ads-tag action under-fires for its own reasons (enhanced conversions matching), so the ratio isn't a clean inflation factor. But the drop from 6.25× to 2.24× after the counting rule changed is the size of the effect. The GA4 action had been reporting something like three to six times the unique submissions for as long as it had existed.
Smart bidding was optimising to that number.
The page that had six forms on it
While inspecting the site we pulled the main panel landing page and counted the forms.
Page | Page weight | Load time | Contact Form 7 instances |
|---|---|---|---|
Existing panel page | 245 KB | 0.62 s | 6 ( |
Rebuilt panel page | 45 KB | 0.26 s | 1 ( |
Six separate form IDs on one page. Nobody designed that; it accreted over rebuilds. Beyond the speed cost, it raised a question the tracking couldn't answer: did all six redirect to /thank-you/? Did all six fire the event? If two of the six didn't, part of the measured conversion rate was a measurement gap, not a page problem. Consolidating to one form removed the question.
The fix, in four layers
Each layer closes a hole the previous one leaves open.
Layer | Change | What it stops | What it doesn't |
|---|---|---|---|
1 | GA4 key event counting: once per session | Refresh, back button, resubmit in the same session | A return visit tomorrow |
2 | GTM trigger: fire only if a query parameter the redirect sets is present | Bare | A crafted URL with the parameter |
3 | Fire on the form submit event, not the destination pageview | Every pageview-based inflation | Nothing significant. This is the correct design |
4 | Redirect carries a one-time token the server validates | Everything, including crafted URLs | Needs server-side work |
Layer 1 is a dropdown in GA4 and was applied in this account within a day. Layer 2 is a GTM trigger condition, five minutes with container access. Layer 3 is the right long-term design and is what the rebuilt page should use. Layer 4 is belt-and-braces for high-value forms.
The trap after you fix it
Having changed the GA4 counting method to once-per-session, we checked the Google Ads side to confirm. It still read:
Field | Value |
|---|---|
| MANY_PER_CLICK |
Days later, unchanged. The natural conclusion is that the fix hadn't propagated. It had. For GA4-imported conversion actions, the Ads-side counting field does not reflect the GA4 setting. GA4 does the counting and exports the result; the Ads label stays at whatever it was. Do not use that field to verify a GA4 counting change. Use volume, compare the action against a one-per-click control over a week and watch the ratio.
The archaeology in the conversion list
Inspecting the account's conversion actions turned up the previous attempts to solve this:
Action name | Status | What it was |
|---|---|---|
| Archived | First version of the thank-you pageview event |
| Archived | Someone tried fixing the counting with a second event instead of a setting |
| Archived | A third variant |
| Archived | The form plugin's own submit event, layer 3 above, built and then abandoned |
| Live | The current one, still on pageview |
Someone had built the correct solution, cf7_form_submission, and then it was archived in favour of the pageview version. Which is the pattern: the pageview event is easier to verify in the interface because you can load the page and watch it fire. That ease is exactly the problem.
How to check your own site
curl -Ithe thank-you URL. If 200, keep going.Open it in a private window. If it loads cleanly, so does it for everyone.
Open GTM, find the trigger on your conversion tag. If it's "Page View: Page Path contains /thank-you", you have this.
Check the counting type on the conversion action in Google Ads. If it's "Every" on a lead form, it's inflated.
Segment conversions by action over the last 30 days. If two actions are within a factor of two of each other and both fire on the form, they're probably the same event.
Symptom | Likely cause |
|---|---|
Conversion rate looks too good | Pageview trigger + "every" counting |
Conversions arrive in clusters on the same click | Refresh or resubmit |
Conversions with no matching lead in the CRM | Non-submission visits to the URL |
Two form actions with different totals | Different counting rules on the same event |
Which event to fix, when five look the same
Once the counting problem was identified, the fix was a dropdown in GA4: change the key event's counting method from "Once per event" to "Once per session." The hard part turned out to be knowing which event.
The GA4 property carried five events with nearly identical names, four of which had at one point been imported into Google Ads and later archived:
GA4 event name | Imported into Ads? | Status |
|---|---|---|
| Yes | Live. This is the one |
| Yes | Archived |
| Yes | Archived |
| Yes | Archived |
| Yes | Archived |
Change the counting method on Thank_You_GA4 and nothing happens to the live conversion. The name that matters is the one the live Ads conversion action points to: visible on the conversion action's detail page in Ads, or via conversion_action.google_analytics_4_settings.event_name in the API. Check that before you change anything in GA4.
Everything went through GTM, which is the control point
One structural detail from the server inspection is worth stating plainly. The site's source contained no hardcoded tag IDs at all: no gtag() calls, no AW- conversion IDs, no G- measurement IDs. Everything fired through a single GTM container.
What this means | Consequence |
|---|---|
All conversion logic lives in GTM | GTM access is the only place the trigger can be fixed |
No tag fires without GTM | If GTM is blocked (consent, ad blocker), no conversion is recorded, a separate under-counting problem |
One container to audit | The full picture is in one place, which is the good news |
If you're auditing a site and find the same thing, get GTM access before anything else. Without it you can diagnose the problem but not fix it, and every recommendation stays hypothetical.
The principle
A conversion should fire on the thing you're paying for (a submission, a call, a purchase), not on a page that usually follows it. The moment the trigger is a URL, you're counting visits to a URL, and a URL is something people can visit for any reason at all.
Thirty seconds with curl tells you whether that's your setup. If it is, every downstream number in the account is built on it.
Related services
Filed under



