Why form tracking is broken almost everywhere
Ask an agency how they track form submissions and the answer is almost always a version of the same thing: a goal fires on the thank-you page. It works, it has worked for fifteen years, and it fails silently in about six different ways that nobody notices until a quarterly review.
The form submits over AJAX and never navigates, so the thank-you page never loads. The client's developer changes the confirmation from a redirect to an inline message. A plugin update alters the success URL. Somebody adds a second form with a different confirmation. The tag manager container gets republished by a contractor and the trigger does not come back. An ad blocker eats the analytics call. In every one of those cases leads keep arriving in the client's inbox and stop arriving in your reporting, and the first sign is a month that looks inexplicably bad.
That failure mode is worse than a plain outage because it is asymmetric. The client sees their enquiries. You see a drop. So the conversation is not "the tracking broke", it is "why did leads fall off a cliff in March", and you are defending work that in fact produced exactly as many leads as February.
The other half of the problem is that the thank-you page knows nothing. By the time it loads, the UTM parameters are gone from the URL, the referrer is your own domain, and the only thing you can record is that a form was submitted. Which campaign produced it is a question the thank-you page cannot answer, so most setups answer it with last-click session data and hope.
The fix is not a better trigger. It is capturing the submission where the submission actually happens, with the visit's history already attached — and doing it in a way that does not depend on the client's form plugin behaving the way it did the day you set it up.
How the capture works
Three layers, and you can use any one of them on its own. Most sites end up with two, because belt and braces on lead capture is cheap and losing a lead is not.
The snippet records the visit
The same script tag that powers dynamic numbers stores the session: referrer, UTM parameters, gclid, fbclid, msclkid, landing page, device and the pages read. This happens on arrival, while the information still exists in the URL — not later, when it has been navigated away.
Forms are found, not configured
Rather than asking you to name every form on every client site, the tracker detects them and reports what it found. You confirm which ones are lead forms. A new form added six months later shows up as detected rather than silently going untracked.
The submission is captured on submit
At the moment the visitor submits — not on the page that may follow — the fields you have chosen are posted with the session attached. An AJAX form, an inline confirmation and a redirect all behave identically, because none of them is being relied on.
WordPress sites can skip the browser entirely
The plugin hooks the form handlers server-side, so the lead is recorded by PHP on the client's own server. Ad blockers, script errors and JavaScript that failed to load stop being able to lose a lead at all.
Tap-to-call is counted as its own lead type
A tap on a tel: link on mobile is a real intent signal and a genuinely different event from a completed call. It is recorded separately, so your reporting can show both without pretending a tap is a conversation.
What ends up on the lead record
The value is not the count. It is that every row can be interrogated after the fact, when the client asks why one month closed better than another.
The full acquisition path
Source, medium, campaign, term and content, plus the raw click identifier where the platform passed one. Not just "google / organic".
The landing page and the path
Which page they arrived on and which pages they read before filling the form. A lead from the pricing page behaves differently from one off the homepage, and the sales team knows it even if the report does not.
The form and the fields
Which form, and the fields you chose to keep. You decide what is captured, which matters when a client's form asks for things you would rather not store.
Device and context
Mobile or desktop, and the time in the client's own timezone rather than the server's — so "we get enquiries in the evening" is a fact you can check.
Lead value and score
Assign a value per form or per lead so cost-per-lead becomes cost-per-pound-of-pipeline rather than cost-per-row.
Spam separated from real
Obvious junk is scored and set aside rather than deleted, so the lead count you report is real conversations while the audit trail stays intact.
The tap-to-call problem, and why most tools get it wrong
On a mobile phone, a large share of enquiries to a local business start with a tap on a telephone link. Most analytics setups count that tap as a conversion, and most agencies report it as one. It is a defensible proxy and it is not a lead.
A tap opens the dialler. What happens next is unobserved: the visitor rings, or reads the number and decides to call later, or taps by accident while scrolling and cancels. On desktop it is worse — a tel: link click on a laptop usually does nothing at all except open a dialogue about which application should handle it. Counting those equally with completed calls inflates the number you report, and the inflation is not evenly distributed across channels, so it distorts the comparison you are using to allocate budget.
The right treatment is to record the tap as its own event, with its own attribution, and to report it beside completed calls rather than merged into them. Then a client can see that mobile paid traffic produces a lot of taps and comparatively few connected calls — which is usually a sign the business is not answering the phone in the minutes after somebody taps, and is one of the more actionable things you will find in an account.
When the same visitor taps and then a tracked number rings, the two events belong to the same session, so the connected call is attributed properly and the tap is not double-counted as a second lead. That reconciliation is the part hand-rolled setups almost never do.
Working with the forms clients already have
Nobody is going to let you rebuild their contact form because your reporting would prefer it. The capture is designed around that.
- Any form builder. Gravity Forms, Contact Form 7, WPForms, Ninja Forms, Elementor, HubSpot embeds, a hand-written HTML form — all of them submit, and the submit is what is captured.
- Any platform. WordPress, Webflow, Squarespace, Shopify, a bespoke build. Anywhere a script tag can go, which is everywhere.
- No thank-you page required. Inline confirmations, modal success messages and AJAX submits all work, because nothing depends on a subsequent page load.
- No tag manager required. Use one if you like. Not needing one removes a container, a publish step and a person who can break your tracking without telling you.
- Choose what is stored. Field-level control over what is captured and kept, which matters when a client's intake form asks for information you would rather never hold.
- Leads are pushed onward. Straight into the CRM, into Slack, by SMS or by email the moment they land — so the business responds while the enquiry is warm.
Three ways to capture, and when to use each
Most sites want the snippet. WordPress sites should have both. The webhook exists for the cases where somebody else owns the form entirely.
| Method | Where it runs | Survives | Use it when |
|---|---|---|---|
| JavaScript snippet | The visitor's browser | Redirects, AJAX, inline confirmations | The default. One tag, every form on the site. |
| WordPress plugin | The client's server | Ad blockers, script failures, JS errors | The client is on WordPress. Belt and braces — use it with the snippet. |
| Server webhook | Wherever the form is processed | Everything, including no website at all | A third-party funnel, a form on somebody else's platform, or a bespoke intake system. |
What this does not solve
It does not attribute a lead that never touched the website. Somebody who emails the address on a van, or walks in, is outside this dataset and always will be. Where that is a meaningful share of a client's enquiries — and for some trades it is most of them — say so in the reporting rather than letting the tracked number imply it is the whole picture.
It does not tell you whether the lead was any good. A form fill is a form fill; whether it turned into a job is something only the client's follow-up knows. The lead pipeline lets them mark it, and CRM delivery pushes it somewhere they were going to look anyway, but neither can make somebody update a record.
It does not defeat a determined privacy blocker on the browser side. That is precisely why the WordPress path exists: a submission handled in PHP on the client's own server is not something a browser extension can intercept. Where the client is not on WordPress and blocking is a concern, the server webhook does the same job.
And it does not fix a form that is bad. If the enquiry form asks for eleven fields including a fax number, tracking it accurately will simply give you an accurate picture of a form nobody completes. That is still useful — it is usually the first thing worth changing — but the tracking is not the fix.
Spam, and the number you are actually reporting
Every public form on the internet gets submitted by robots. On a plumber's contact page it might be two a week; on a personal injury firm running paid search it can be a third of everything that arrives. If those rows go into the lead count you hand the client, two things happen and both are bad. The count is wrong, which is obvious. And the cost per lead is wrong in a way that is not obvious, because spam is not distributed evenly across your channels — it lands on the pages that rank, which means organic looks cheap and paid looks expensive for reasons that have nothing to do with either.
Deleting the junk is the intuitive fix and it is the wrong one. A deleted row cannot be reviewed, and the first time a client says "that lead you binned was our biggest job this year" you will want the record. Junk is scored and set aside instead: still stored, still attributed, excluded from the headline count, and one click from being restored when the filter gets it wrong. Which it will, occasionally, because a real enquiry typed in eleven seconds with no punctuation looks a great deal like a bot.
The scoring itself is deliberately boring — submission timing, field content, whether the session had any browsing history behind it, whether the same payload has arrived before. Nothing here claims to be clever. The useful part is not the accuracy of any individual verdict, it is that the decision is visible: you can open the excluded pile, see why each row is in it, and disagree. A filter you cannot audit is a filter that quietly edits your reporting.
What this buys you in a client meeting is the ability to say "you received 74 enquiries; 61 were real, and here are the 13 that were not" — which is a far stronger position than a single number the client half-suspects is inflated. Agencies lose accounts over lead quality arguments more often than over lead volume, and the argument is usually unwinnable because neither side can see the same list.
Consent, storage and the parts you should think about
Form capture means holding other people's personal data on behalf of your client, and the honest framing is that this makes you a processor with obligations rather than a tool vendor with a feature list. Three practical consequences are worth stating plainly before you deploy it across an account base.
First, capture less. Field-level control exists so that you can decline to store the things you do not need — a date of birth on a clinic enquiry form, a case description on a legal intake, a national insurance number somebody's developer added to a form for reasons lost to history. A field you never capture is one you never have to secure, never have to export in a subject access request, and never have to delete. The default should be the minimum that lets the client follow up and lets you attribute the lead: name, one contact method, and the source.
Second, decide where consent sits. If the client's site runs a cookie banner, the tracking snippet should respect it, and it can be held until consent is given. That costs you attribution on the visitors who decline — genuinely, not notionally — and the right response is to report the gap rather than paper over it with modelled numbers. Server-side capture through the WordPress plugin still records the submission itself, because a person filling in a contact form is providing their details deliberately; what it will not have is the browsing history behind it.
Third, be able to delete. Leads can be removed on request, individually or for a whole client, and the deletion is real rather than a hidden flag. If you are running an agency across dozens of accounts, the ability to answer "delete everything you hold about this person" in under a minute is not a compliance nicety, it is the difference between a routine request and a bad afternoon.
None of this is legal advice and none of it replaces a privacy policy the client actually publishes. It is the set of controls that lets you do the sensible thing without arguing with the software.
Where it sits in the wider picture
Forms and taps are two of the three ways a local business gets enquiries; calls are the third, and usually the largest. Capturing all three against the same session is what makes a total lead count meaningful — and what stops a channel that produces calls from looking worse than one that produces forms simply because forms were easier to measure.
Once every lead type carries its source, everything downstream gets sharper. Cost per lead becomes a real division rather than an estimate over the half you could see. Multi-touch attribution has a complete set of endpoints to work from. The client's shared report shows one number for enquiries with an honest breakdown underneath, instead of a call figure and a form figure that were counted by different systems on different definitions.
And because the same session powers the tracked phone number, a visitor who reads two pages, fills a form and then rings is one person in the data — not a form lead and a separate mystery call.
Common questions
Will this work with Gravity Forms, Contact Form 7 or WPForms?
Yes, and with Ninja Forms, Elementor forms, Formidable, HubSpot embeds and plain HTML forms. The capture hooks the submission rather than integrating with a specific plugin, so a form builder you have never heard of works the same way. On WordPress the plugin additionally hooks the handlers server-side, which is the more reliable of the two.
Do I need Google Tag Manager?
No. You can use it if it is already part of your workflow, but nothing here depends on it. Not needing a container removes a publish step, a second set of credentials and a category of failure where somebody republishes the container and your triggers quietly do not come back.
What happens if the client changes their thank-you page?
Nothing. The submission is captured at the moment of submit, so the page that follows is irrelevant — it can be a redirect, an inline message, a modal, or nothing at all. This is the single most common way conventional form tracking breaks, and it is designed out rather than monitored for.
Does ad blocking stop form tracking?
It can stop browser-side capture, which is why the WordPress plugin records submissions server-side and why the webhook exists. On a WordPress site with the plugin installed, the lead is recorded by PHP on the client's own server before anything reaches the browser, so an extension cannot intercept it.
How is a tap-to-call counted?
As its own lead type, separate from completed calls. A tap opens the dialler and what happens next is unobserved, so merging taps into your call count inflates the figure — unevenly across channels, which distorts exactly the comparison you use to set budget. If the same visitor then rings a tracked number, the two are reconciled to one session rather than counted twice.
Can I control which form fields are stored?
Yes, per form. You choose which fields are captured and kept. That matters for intake forms that ask for information you would rather not hold at all — a field you do not capture is one you never have to secure, export or delete.
Will duplicate submissions be counted twice?
No. A visitor who double-taps submit, or who is captured by both the snippet and the WordPress plugin, produces one lead. Deduplication happens against the session, which is the same mechanism that keeps a form fill and a subsequent phone call from the same person from reading as two unrelated leads.
Can leads be pushed straight into a CRM?
Yes, with the attribution attached — Salesforce, HubSpot, Pipedrive, GoHighLevel, Zoho and others, plus Slack, SMS and email notification. The point is that the client's sales team sees the lead and its source somewhere they were already looking, rather than having to open your dashboard.
How is form spam handled?
It is scored and set aside rather than deleted — still stored, still attributed, excluded from the headline count, and restorable in one click when the filter gets it wrong. That matters more than the accuracy of any individual verdict, because spam is not spread evenly across channels and silently including it makes organic look cheap and paid look expensive for reasons unrelated to either.
Does form tracking respect a cookie consent banner?
Yes — browser-side capture can be held until consent is given, which genuinely costs you attribution on visitors who decline. The honest handling is to report that gap rather than fill it with modelled numbers. Server-side capture on WordPress still records the submission itself, since a person completing a contact form is providing their details deliberately, but it will not carry the browsing history behind it.
Does this replace analytics?
No, and it should not. Analytics is good at traffic and behaviour across a whole site. This is about individual leads and where each one came from — a per-row question that sampled, aggregated session data cannot answer. Most agencies run both and use each for what it is good at.