Form Submits That Survive the Redirect: Tracking Conversions in GTM
A form submit goes missing when the page redirects before GA4's tag sends its hit. Here's how to hold a native post until Google Tag Manager answers, how script-posted forms should send the event instead, and how to verify it in GA4 DebugView.
A form submit goes missing when the browser leaves the page before the GA4 tag has sent its hit. The fix is to hold the submit for a moment: push the event to the data layer with eventCallback and eventTimeout, release the form when Google Tag Manager answers, and confirm receipt in GA4 DebugView. If GA4 itself is new ground, start with my definitive guide to Google Analytics 4.
This how-to is built from Google's, MDN's and HubSpot's own documentation, and each step links to the page it comes from. I've shipped one version of it, on the contact forms of my own site, and I'll say where that applies. Google Tag Manager form submission tracking breaks in a handful of predictable ways, and the redirect is the expensive one because it fails without an error.
Key takeaways
- A redirect can unload the page before the GA4 request leaves. Google's Tag Manager Help says that without Wait for Tags, "slow tags may not fire before the form submit causes the next page to load."
- GTM's Form Submission trigger only sees a real browser
submitevent, so forms posted by script, or inside an iframe, need a data layer event instead. - For a form that posts natively, the fix is a four-step pattern I call Hold, Send, Release, Reset: pause the post, push the event with
eventCallback, submit when GTM answers or after 2 seconds, and clear the guard on back navigation. A form that posts by script sends the event from its own success handler instead. - "Fired" in GTM preview means the tag ran, not that GA4 received anything. Verify in DevTools with Preserve log on, then in GA4 DebugView.
- In a B2B funnel the browser event is only the first link. Carry the GA4 client ID into the CRM so
qualify_leadand later stages can be sent against the same visitor.
Why does a form submit disappear when the page redirects?
A form submit disappears because three things race when someone clicks Submit: the browser's navigation to the next page, GTM working through its data layer queue, and the GA4 request going out. If the navigation wins, the page unloads with the hit still unsent. Nothing errors. The conversion just never arrives.

Google documents the queue. Tag Manager processes data layer messages "on a first-in, first-out basis," one at a time, according to its data layer guide. MDN documents the other half: when a page is about to unload, "the browser may choose not to send asynchronous XMLHttpRequest requests" (MDN, Navigator.sendBeacon()). Beacon-style transport helps a tag that has already fired. It can't help a tag that never got its turn.
I found this exact failure on my own site. In a launch check on October 25, 2026, no form_submit event had reached GA4 since my GTM tags went live, even though the success page's page view was there. My forms are native Netlify posts, so the page left in the same moment the event was pushed.
For a B2B SaaS company the lost event is rarely small. The 6sense 2026 B2B Buyer Experience Report found vendor contact happens, on average, 59.2% of the way through a 10.3-month buying cycle, and that buyers initiated 82% of first contacts. Gartner reported in 2020 that buyers "typically spend only 17% of their time meeting with potential suppliers" (Gartner). The demo request is often the first moment a buyer tells you who they are. Lose it and you lose the record that ties months of anonymous research to pipeline.
Which kind of form do you have?
The right fix depends on how the form leaves the page, so identify that before you build anything. Open DevTools, submit a test entry, and watch whether the page posts natively, posts by script, or never leaves an iframe. This table maps each case to what GTM can see.
| How the form submits | What GTM's listener sees | Fix I'd use |
|---|---|---|
| Native HTML post to a thank-you URL (Netlify Forms, many CMS forms) | A real submit event | Form Submission trigger with Wait for Tags, or Hold, Send, Release, Reset in site code |
Script validates, posts with fetch, then sets window.location | Nothing, or a submit whose default was prevented | Push a data layer event after the server confirms, with eventCallback |
Script calls form.submit() directly | Nothing, because no submit event is raised | Push the event yourself before calling submit() |
| Embedded HubSpot form with a redirect | HubSpot's own events, not a page-level submit | Listen for hs-form-event:on-submission:success and push to the data layer |
| Scheduler or form inside a cross-domain iframe | Nothing on the parent page | The vendor's postMessage events, or tags inside the iframe |
Simo Ahava, whose GTM blog is a standard reference, is partner and co-founder at 8-bit-sheep and co-founder of Simmer, an online course platform for technical marketers. He put the limitation plainly: "if some script on your site hijacks the form event and proceeds with some proprietary Ajax function, for example, GTM's listener will not be able to pick it up" (Simo Ahava, Trigger Guide for Google Tag Manager). Julius Fedorovicius, founder of Analytics Mania, goes further. In "99% of cases where I worked, I used a different tracking method" than the built-in listener, he writes in his GTM form tracking guide.
HubSpot documents its success event in the global form events reference, along with a getRedirectUrl method for forms set to redirect after submission. Older HubSpot embeds post a browser message with the type hsFormCallback and the event name onFormSubmitted instead.
Is Wait for Tags enough on its own?
Wait for Tags is enough for a plain native post with no other script on the form. Google's Form submission trigger help page says the option delays "the form submission until all the tags that depend upon this trigger have fired or the specified timeout has elapsed, whichever comes first." The default ceiling is 2,000 milliseconds, as Ahava notes in his form engagement guide.

To set it up in GTM, go to Triggers, then New, then Form Submission. Tick Wait for Tags and Check Validation, scope "Enable this trigger when" to the page path where the form lives, and set "This trigger fires on" to Some Forms with Form ID equals your form's ID. Google recommends that scoping "for best performance."
Check Validation matters more than its name suggests. Google says it makes the trigger fire "only if the form is successfully sent," and Ahava explains the mechanism: the trigger fires only when no other script has called event.preventDefault() on the submit (Simo Ahava, 100+ Google Tag Manager learnings). That's the right behavior for a form that fails validation. It's also why an AJAX form never fires the trigger at all.
On the timeout, Ahava's advice still holds: "I do not recommend that you increase this timeout, because if your dependent tags don't fire within 2 seconds, the fault is with them, not with GTM or your form" (Simo Ahava, Advanced form tracking in Google Tag Manager).
How do you hold the post until the tag sends?
The hold-the-post fix pauses the submit, pushes the event with a callback, and lets the form go when GTM confirms or a timer runs out. I call it Hold, Send, Release, Reset, and each step closes one specific way the fix itself could break a form. It's for forms that post natively. Forms that post by script get a shorter fix, covered after the code.
- Hold: On submit, check that GTM loaded (
window.google_tag_managerexists) and that no other script already cancelled the submit. Then callpreventDefault()and set a flag so a second click does nothing. Use the hold only on forms that post natively, which thedata-submit="native"selector enforces. Never put it on a form that posts by fetch or AJAX: if that form's own submit listener runs after this one, it sends its fetch and this code calls a nativesubmit()as well, so the lead is submitted twice. - Send: Push your event to the data layer with
eventCallbackandeventTimeout. GTM runs the callback after the tags on that event finish. - Release: Submit with
HTMLFormElement.prototype.submit.call(form), guarded so it runs once, plus your own 2-second timer in case GTM never answers. - Reset: Clear the flag on
pageshowwhen the page comes back from the browser's back/forward cache.

Here's the shape of it for a native-post demo form marked data-track="demo" and data-submit="native":
document.addEventListener('submit', function (e) {
var form = e.target
if (!form.matches('form[data-track="demo"][data-submit="native"]')) return
window.dataLayer = window.dataLayer || []
// Another script already cancelled this submit: no hold, no event.
if (e.defaultPrevented) return
// GTM blocked or not loaded: no hold.
if (!window.google_tag_manager) return
e.preventDefault()
if (form.dataset.held === '1') return
form.dataset.held = '1'
var released = false
function release() {
if (released) return
released = true
HTMLFormElement.prototype.submit.call(form)
}
window.dataLayer.push({
event: 'generate_lead',
form_id: form.id,
lead_source: 'demo_request',
eventCallback: release,
eventTimeout: 1500
})
setTimeout(release, 2000)
})
window.addEventListener('pageshow', function (e) {
if (!e.persisted) return
document.querySelectorAll('form[data-held]').forEach(function (f) {
delete f.dataset.held
})
}) Each guard maps to documented behavior. Ahava's eventTimeout tip warns that without a timeout, "if any of the Tags that fire on this payload fail, timeout, or in some other way fail to inform GTM that they have completed, the redirect will never happen," and it recommends the google_tag_manager check for pages where GTM never loaded. MDN explains why submit() is the right release: it raises no submit event, so the handler can't loop, and calling it through the prototype avoids the trap where a field named submit masks the method (MDN, HTMLFormElement.submit()). Don't swap in requestSubmit(). It fires the submit event again, and the held flag would swallow it.
One side effect to plan for. MDN notes that a submit button's own name and value aren't sent when you call submit(), so if your backend reads the button, copy that value into a hidden input first.
The Reset step exists because back navigation is common. web.dev's back/forward cache guide reports that "1 in 10 navigations on desktop and 1 in 5 on mobile are either back or forward." A page restored from that cache keeps its JavaScript state, held flag included, so without pageshow the form looks dead to anyone who comes back to fix a typo.
A note on sources. Google's current data layer guide doesn't document eventCallback and eventTimeout, so Ahava's tip is the practical reference for the GTM keys. If your site runs gtag.js instead, the same idea is officially documented: Google's gtag.js parameter reference lists event_callback with a form-submit example and event_timeout with a value of 2000.
My own version shipped on my site's contact forms on October 25, 2026, with a 1.5-second eventTimeout and a 2-second fallback. In a local test with GTM stubbed, a callback that answered after 300 ms released the post at 328 ms, and a GTM that never answered released it at 2,002 ms. That's the cost of the fix: usually a few hundred milliseconds, never more than two seconds.
A form that posts by script skips the hold entirely. Put the push in that script's own success handler, after the server answers with a 2xx status, so a failed validation or a server error never counts as a lead. MDN's Response.ok reference says ok is true for any status from 200 to 299. Here's the success branch, with the redirect moved inside eventCallback and the same timeout guard:
// Inside the form's own handler, after fetch() returns res
if (res.ok) {
var sent = false
var goToThanks = function () {
if (sent) return
sent = true
window.location.href = '/thank-you/'
}
window.dataLayer = window.dataLayer || []
window.dataLayer.push({
event: 'generate_lead',
form_id: form.id,
lead_source: 'demo_request',
eventCallback: goToThanks,
eventTimeout: 1500
})
setTimeout(goToThanks, 2000)
} Then wire up GTM. Create a Custom Event trigger with the event name generate_lead, two Data Layer Variables for form_id and lead_source, and a GA4 Event tag named generate_lead that sends both as parameters. Google's recommended events reference says to use generate_lead "to track initial lead acquisition, such as form submissions, newsletter sign-ups, or demo requests." Then mark it as a key event in GA4 Admin, per Google's key events help page.
How do you verify it in GTM preview and GA4 DebugView?
Verify on the receiving side, not just in GTM. Tag Assistant's Tags Fired list means GTM ran the tag. It doesn't mean the request left the browser before the page unloaded, which is the exact failure you're fixing. Run these checks in order:
- Start GTM Preview, which opens Tag Assistant, and submit a real test entry. Confirm
generate_leadappears in the event list with the GA4 tag under Tags Fired. - In Chrome DevTools, open Network, turn on Preserve log, and filter for
collect. Submit again and confirm a request carryingen=generate_leadwent out before the thank-you page loaded (Chrome DevTools Network reference). - Open GA4 Admin, then Data display, then DebugView. Preview mode turns on debug mode for your device, and the event should land in the Seconds stream with
form_idandlead_sourceattached (Google, Monitor events in DebugView). - Submit with a required field left blank. Nothing should fire. Analytics Mania treats testing a failed submission as a rule of thumb, because a loose trigger inflates conversions.
- Block
googletagmanager.comwith DevTools request blocking and submit. The form should still post within about two seconds. - Submit, press Back, and submit again. You should get one event per submit and a form that still works.
- Grant analytics consent before you test. Google notes that events don't show in debug mode when consent mode is in place and the visitor hasn't consented to analytics cookies.
- After a few days, compare GA4's
generate_leadcount with the submissions in your form tool or CRM for the same dates.
What usually breaks?
Most broken form tracking falls into a short list, and each item takes a minute to check.
- Double counting: A page view tag on the thank-you URL and an event tag on the submit both count as conversions. Pick one key event.
- The wrong key event: GA4's enhanced measurement sends
form_submit"when the user submits a form," for any form on the page (Google, enhanced measurement events). Keep it as a diagnostic, not as the conversion Google Ads bids on. - A slow pixel holding everyone:
eventCallbackwaits for every tag on that event, so one stalled third-party tag sets the wait for all of them. The timeout caps the damage. - Blockers and consent: GWI reported in April 2025 that nearly 1 in 3 consumers use ad blockers, 21% regularly and 11% now and then, down from 37% in 2021 (GWI). No browser fix recovers those visits, which is where the CRM record and server-side tracking come in. Denied consent has a similar effect, and how Consent Mode v2 changes attribution covers what modeling does and doesn't recover.
- A field named submit: It masks
form.submit(), per MDN, so the release throws an error unless you call it through the prototype.
Should you count thank-you page views instead?
A thank-you page view is a fine backstop and a weak primary conversion. It survives the redirect because it fires after the navigation, which is why so many tutorials and AI answers recommend it, and Analytics Mania calls it "the simplest and most reliable method when available."
The trade-off shows up in a B2B program. A thank-you URL counts reloads, bookmarks and anyone the link gets forwarded to, and it can't say which form, offer or lead source produced it unless the URL does. If one /thanks/ page serves the demo form, the trial form and the newsletter, the page view can't tell Google Ads which one to bid toward. I'd make the event the key event and keep the page view as a reconciliation check.
How do form submits connect to MQL, SQL and opportunity?
The browser event is only the first link in the funnel, so the fix has to carry a join key into the CRM. GA4 has recommended lead generation events for the rest of the chain: generate_lead for the form, then qualify_lead, working_lead and close_convert_lead as the lead moves. Google says they're "useful for business-to-business sales," and that "sending these events populates the Lead acquisition report."
Here's how that looks for a B2B SaaS company, as an illustration rather than anyone's real numbers. A visitor requests a demo, and the form carries two hidden fields: the GA4 client ID, which gtag's get command returns, and the gclid from the ad click. Marketing marks the lead an MQL, sales accepts it as an SQL, and an opportunity opens. The CRM sends qualify_lead at MQL and working_lead at SQL to GA4 through the Measurement Protocol with the stored client ID. Google has no opportunity event, so the opportunity rides on working_lead or a custom event, and the gclid goes to Google Ads as an offline conversion import.
Then reconcile. Say the CRM shows 120 demo requests in a month and GA4 shows 84 generate_lead events. The 36-request gap, 30% of the CRM count, is the number to explain. Some of it is blockers and denied consent. The redirect race is the part you can close with the hold above.
On a paid landing page the form is rarely the only path, either. I built tracking like this for a high-intent landing page for an energy company's search campaign, tracked on forms and calls, where calls needed their own conversion action next to the form. Once the events are clean, the next question is whether the spend caused them, which is what incrementality testing for B2B SaaS answers.
What does it cost to track form submits properly?
Most of this stack is free, and the paid line items are the form tool and developer time. Google Tag Manager runs on 44.7% of all websites, a 99.6% share of sites using a tag manager W3Techs monitors (W3Techs, November 4, 2026), so the tooling is rarely the obstacle. Prices below are as listed on each vendor's page on November 4, 2026.
| Tool | Role in this setup | Price (as of Nov 4, 2026) |
|---|---|---|
| Google Tag Manager | Triggers, data layer, Preview | Free |
| Tag Manager 360 | Enterprise controls and support | Quoted by Google sales, no public price |
| Google Analytics 4 | Key events, DebugView | Free |
| Analytics 360 | Higher limits and support | Quoted by Google sales, no public price |
| Tag Assistant | Preview and debug | Free |
| Chrome DevTools | Network check with Preserve log | Free, built into Chrome |
| HubSpot Marketing Hub | Forms and CRM | Free forms tool. Starter listed at $20 per seat per month. Professional $800 a month billed annually or $890 billed monthly, with 3 core seats. Enterprise from $3,600 a month |
| Netlify Forms | Form handling on a static site | Submissions free on all credit plans since April 14, 2026, previously 1 credit each |
The cost of skipping it is harder to see. Every lost demo request is a conversion your bidding never learns from and a lead your reports can't tie back to the campaign that produced it.
Frequently asked questions
Does holding the form slow it down?
Only briefly. The hold lasts until GTM's callback runs, usually a few hundred milliseconds, and the timeout caps it. Google's default for Wait for Tags is 2,000 milliseconds, and Ahava advises against raising it.
Why does GTM preview show the tag fired when GA4 shows nothing?
Because "fired" is GTM's view, and the page can unload before the request leaves. Check the Network panel with Preserve log on, then DebugView. Also check consent, since DebugView hides events when analytics consent hasn't been granted.
Can server-side GTM fix the redirect problem?
Not by itself. A server container receives hits from the browser, so the browser still has to send the first request before it navigates. Server-side tagging helps with durability and blockers, which is a separate decision from this fix.
Which GA4 event should a demo request use?
Use generate_lead. Google's reference names demo requests explicitly, and the recommended name lets GA4 fill the Lead acquisition report once the later lead stages arrive from your CRM.
How do I track a HubSpot form that redirects to a thank-you page?
Listen for HubSpot's hs-form-event:on-submission:success event, or the legacy onFormSubmitted message, push generate_lead to the data layer, and keep the thank-you page view as a backstop. If testing shows the redirect still wins the race, switch the form to an inline thank-you message and do the redirect yourself inside eventCallback, with the same timeout guard.
If you'd rather have this wired and verified for you, it's the kind of measurement work I do under analytics and reporting.