Partial lead capture is the practice of recording form field values as a visitor types them, rather than waiting for the submit button. A script on the page listens for input on each field, waits for a short pause, and posts the value to your server. When the visitor leaves, anything still pending is flushed before the page goes away.
The result is that the person who typed an email address and then closed the tab is a lead you can contact, not a bounce you can only count.
Nothing about the form itself changes. No extra step, no email-first gate, no popup. The difference is entirely in when the data leaves the browser.
Why does the submit button lose so many of your leads?
Because submit is the only moment most tracking is listening for, and it’s the last moment in the sequence. Everything before it — the email typed into field one, the phone number half-entered in field three — exists in the browser and nowhere else. If the visitor leaves, the browser discards it and no system you own ever knew it existed.
This is a collection problem wearing a tracking problem’s clothes: analytics can tell you the form was viewed and not submitted, but never who, because the who was in the fields.
The reasons people stop are mostly boring: the form asks for a phone number, the next field is a dropdown they can’t answer, a call comes in. What they share is timing. Abandonment happens after the visitor has identified themselves and before your systems are told — which is why form abandonment is a revenue question rather than a UX footnote.
A visitor who types an email has done the hardest thing you will ever ask of them. Treating that as a non-event because they didn’t finish the last field is a choice, not a technical necessity.
How does partial lead capture actually work?
Four steps: listen to each field, debounce the values, send them to your own endpoint, and flush anything outstanding before the page unloads. The engineering difficulty is not in the capture — it’s in the leaving.
What triggers a capture?
The input event on each field you care about, plus blur as a backstop. input fires on every keystroke, which is far too often to transmit, so captures are debounced: the script waits a few hundred milliseconds after typing stops, then sends the current value once. A 500 ms debounce turns a fifteen-character email into one request instead of fifteen.
blur matters because the visitor moved on from that field deliberately — the strongest available signal that the value is final rather than half-typed.
What happens when the visitor leaves?
This is the part naive implementations get wrong. If someone types their phone number and immediately closes the tab, the debounced value is sitting in a timer that will never fire.
A correct implementation keeps a terminal flush on pagehide and on visibilitychange when the page becomes hidden, and sends it with navigator.sendBeacon() — a request the browser completes after the page is gone — with fetch(..., {keepalive: true}) as the fallback. The old unload event is not a reliable place for this on mobile; pagehide is the one that fires.
Get the flush wrong and you lose exactly the leads you were trying to save: the ones who left abruptly.
Where does the data go?
To your own endpoint, on your own domain, and it should be normalised the moment it arrives — not when you later try to use it. Email lowercased and trimmed. Phone converted to E.164 with a country code inferred from the session when the visitor didn’t type one.
The payload should also carry the context that makes the lead worth anything: page URL, landing page, referrer, UTM parameters, and any ad click identifiers on the session. A captured email with no source tells your sales team who to call. A captured email with a source tells your media buyer which campaign to scale.

What does a partial lead record actually contain?
More than the contact details, if it’s built properly. A useful partial lead record has four layers:
Contact fields. Whatever was typed — email, phone, first name, company. Email is the minimum viable version: without one, follow-up is impossible.
Field order and timestamps. Which fields were completed, in what order, and when the last one was touched. This is the layer most tools throw away, and it’s the one that tells you where the form is losing people. If the last completed field is always the one before “annual revenue”, you have a diagnosis, not a hunch.
Session attribution. UTM parameters, referrer, landing page, and click identifiers like gclid or fbclid. Without this, a partial lead is a name in a spreadsheet. With it, it’s a name attached to a campaign.
Signal quality. Time on page, interaction depth, whether the browser looked automated, whether the email domain is disposable. Partial leads arrive in higher volume than completed ones, so sorting the serious from the noise is not optional.
Can you build partial lead capture yourself?
Yes. The core is about thirty lines of JavaScript and an endpoint that writes a row. Here’s the shape of it:
// Capture email/phone as they're typed, flush before the page goes away.
const pending = new Map();
let timer;
function queue(name, value) {
pending.set(name, value);
clearTimeout(timer);
timer = setTimeout(flush, 500); // one post per pause, not per keystroke
}
function flush() {
if (!pending.size) return;
const payload = JSON.stringify({
session_id: sessionId, // your own first-party id
page_url: location.href,
fields: Object.fromEntries(pending),
});
const blob = new Blob([payload], { type: 'application/json' });
// sendBeacon survives navigation; keepalive fetch is the fallback
if (!navigator.sendBeacon('/capture/field', blob)) {
fetch('/capture/field', { method: 'POST', body: payload, keepalive: true });
}
pending.clear();
}
document.querySelectorAll('input[type=email], input[type=tel]').forEach((el) => {
el.addEventListener('input', () => queue(el.name, el.value));
el.addEventListener('blur', flush);
});
// The terminal flush. pagehide fires where unload does not.
addEventListener('pagehide', flush);
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') flush();
});
That gets you captures. What it doesn’t get you is everything after them, which is where self-built versions stall:
- The same person captured five times. One visitor, one session, several field revisions, two devices. Without identity resolution you have five “leads” and one human being.
- Iframe forms. If your form is a Typeform, Jotform or GoHighLevel embed, the script above runs inside an iframe whose URL has no UTM parameters. Attribution arrives empty unless the parent page relays it in.
- Multi-step forms. Fields that don’t exist yet when the page loads need delegated listeners, or step two is invisible.
- Bot noise. An open capture endpoint collects whatever automated traffic types into your form, and partial capture has no submit-time gate to filter it.
- Getting the lead somewhere useful. A row in your database is not a lead in your CRM, an event in Meta’s Conversions API, or a task for a salesperson.
None of this is exotic. It’s just considerably more work than the capture itself, which is the part everyone estimates.
Is it legal to capture data before someone hits submit?
Treat it as what it is: collecting personal data. The same rules that apply to a submitted form apply to a partially completed one — the difference in your database is not a difference in the law. This isn’t legal advice, but the defensible position is consistent everywhere.
Disclose it in your privacy policy in plain words. Where your jurisdiction requires consent before non-essential tracking, gate the capture behind that consent rather than firing first and asking later. Capture only fields you have a reason to hold, and never sensitive ones — no passwords, no payment fields. Honour deletion requests against partial records exactly as you would against submitted ones.
The practical test: if you’d be uncomfortable telling the visitor you have their email, the problem is the follow-up you were planning, not the mechanism.
How does PartialLeads capture partial leads?
By capturing fields on input with a terminal flush, then resolving every capture into a person and an attributed lead rather than leaving it as a stray row.
Capture as they type. The tag records fields on input and blur with a 500 ms debounce, and drains any pending field on submit, pagehide or visibilitychange using sendBeacon with a keepalive-fetch fallback. The last field typed is the one most likely to be lost, and it’s the one this exists to protect.
One script, no tag manager. A single tag — around 10 KB gzipped — on the surfaces where lead forms live: ordinary sites, Shopify, WooCommerce, GoHighLevel, Typeform, Jotform, Unbounce, ClickFunnels.
Iframe attribution relay. The tag listens for postMessage attribution from parent frames and broadcasts UTM parameters down into iframes, which is what keeps a Typeform or GHL embed from reporting every lead as Direct.
Normalisation on arrival. Email lowercased and trimmed, phone normalised to E.164 with a country-code fallback derived from the session’s geography. Consistent formatting is what makes later matching possible.
Identity, not rows. Captures resolve into a person across six ranked tiers — visitor ID, email, phone, IP plus user agent, device fingerprint, click ID — anchored by a visitor ID whose first-party cookie backup is server-set, so Safari’s cap on script-written cookies doesn’t shorten it. One person who came back three times is one lead with three touches.
The captured contact is a conversion payload. A partial lead carries a real email and often a phone, so it can be dispatched server-side to the ad platforms as a Lead event through the Conversions API — hashed, deduplicated on a deterministic event ID, retried on transient failures. Which is why form design matters more than the pixel: with email and phone as required fields, PartialLeads consistently delivers event match quality of 9 or above, observed across customer accounts. Email-only forms structurally cap lower, because you can only transmit what you collect.
Qualification before the call list. Two-step AI enrichment scores each lead against your ideal-customer persona and surfaces a match verdict, so follow-up starts with the strong matches rather than with row one.
Honest constraints. A visitor who types nothing identifying cannot be captured — there is no lead in an empty field. Ad-platform match quality still depends partly on the platform’s own graph and on your client-side base tag being present; PartialLeads reconstructs _fbc from an fbclid in the URL, but it does not invent an _fbp cookie that was never set. And partial leads are a different contact experience from inbound submissions — they didn’t ask you to call, which is a follow-up design problem, not a data problem.
Where you see it. Captured-but-unsubmitted people appear on the Leads page with an amber Partial badge, each carrying their captured fields, journey and source, and — once they buy or book — a flip to Completed with revenue attached. A partial lead stops being a hypothetical the first time you open that list and recognise a company name.

| What breaks | The mechanism | Where you see it in the dashboard |
|---|---|---|
| Data only leaves the browser on submit | Field capture on input/blur with a 500 ms debounce |
Leads page, Partial badges |
| The last field typed is lost when the tab closes | Terminal flush on pagehide/visibilitychange via sendBeacon |
Captured fields in order on the lead |
| Nobody knows which field people quit on | Per-field capture timestamps kept in sequence | Lead detail, field entries |
| An iframe form reports every lead as Direct | postMessage UTM relay plus referrer fallback |
Correct source label on embedded-form leads |
| One person looks like several leads | Email lowercased and trimmed, phone normalised to E.164, then a six-tier identity cluster resolves sessions to a person | Journey ribbon, unique contacts |
| The ad platform never learns about the lead | Server-side Conversions API dispatch with deterministic event IDs | CAPI activity log |
| Volume buries the leads worth calling | AI qualification against your ideal-customer persona | Customer Match verdicts, priority inbox |
Recover the leads you're already earning
Tell us what you're trying to track or fix. We'll show you which visitors your forms miss — and how PartialLeads recovers and qualifies them.
Sources
- Meta for Developers — Conversions API, customer information parameters: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters
- Meta for Developers — Conversions API overview: https://developers.facebook.com/docs/marketing-api/conversions-api
- WebKit — Tracking Prevention in WebKit: https://webkit.org/tracking-prevention/