Treat them as one person with two addresses, not as one duplicate to delete. Join the two records on the identifier that did not change — the browser’s visitor ID, a normalised phone number, a click ID — then keep both emails and count the person once.
Deleting the second row is the instinct, and it is usually the wrong half you delete. The ad click is frequently on the record you were about to throw away.
Why does the same person fill your form twice with two different emails?
Because the two fills happen in two different contexts, and the person is not trying to be difficult. A typo on the first attempt. A resend because nothing on screen looked like it worked. A work address at the desk on Tuesday, a personal one on the sofa on Friday. A throwaway inbox for the download and the real one for the demo.
Each of those has a different shape, and the shape tells you how to handle it.
A re-fill happens minutes apart in the same browser. Someone mistyped ava@exmaple.com, got no confirmation, and typed it again correctly. The two records are ninety seconds apart with the same session, the same landing page, the same click.
A second visit happens days apart, often on a different device. Ava clicked your Meta ad on her phone during the week, typed her work email, and abandoned. On Saturday she searched your brand name on her laptop and completed the form with a Gmail address. Same human, two devices, two addresses, and only the first record knows where she came from.
A deliberate alias is neither. Some people keep a burner inbox for gated content and hand over the real one when they want to talk to you. That is not noise; it is a buying signal with a timestamp on it. Running one cleanup rule across all three shapes is the mistake.
What actually breaks when one person becomes two lead records?
Four things break, and they break quietly: your lead count inflates, your cost per lead deflates, source credit splits between two rows, and your sales team contacts the same person twice.
Start with the arithmetic, because it is the one people check. Say you spend $3,000 in a month and your form produces 300 rows: that reads as a $10 cost per lead. If 40 of those rows are people filling in a second time, you bought 260 people, and the real figure is $11.54. Those numbers are illustrative, not a benchmark. A duplicate rate you have not measured always makes acquisition look cheaper than it is, and the error compounds when you compare channels that duplicate at different rates.
Then the credit split, which is worse. The first fill carried the fbclid from Ava’s Meta ad click. The second fill, days later on a laptop she opened from a bookmark, carries nothing. Last-touch attribution looks at the row that converted and credits Direct. Meta gets nothing, the campaign that actually produced her looks unprofitable, and you turn it off. It is the same failure that makes one customer show up as three leads, arriving through a different door.
Then the ad platform itself. If your conversion rules fire on both rows, you send two Lead events for one human. The algorithm is told there are two people worth finding when there is one. It then spends your budget looking for a lookalike of a person it already has.
Then the human cost, the one that actually loses the deal: two reps, two sequences, two “just following up on your enquiry” emails to someone who, as far as she remembers, filled the form once.
Which identifier still joins them when the email doesn’t match?
The one the person did not retype. Email is the strongest join key you have right up until the moment it changes, and then it is worth nothing — so the join has to rest on something carried by the browser, the device or the ad click instead.

Here is the ladder, strongest first.
| Identifier | Joins the two records when | Fails when |
|---|---|---|
| First-party visitor ID | Both fills happen in the same browser | The cookie was cleared, capped by the browser, or the second fill is on another device |
| Phone number (normalised) | They typed the same number both times | You don’t collect phone, or the field is free text and never normalised |
| Click ID (gclid, fbclid, msclkid, ttclid) | The same ad click preceded both sessions | The second visit was direct, organic or from a bookmark |
| IP address + user agent | Both fills are minutes apart on the same network and device | Shared office IPs, household NAT, mobile carrier ranges, VPNs and relay services |
| Device fingerprint | Same device, cookies gone, inside a bounded window | Shared or identical corporate machines |
Two things follow from that table.
The first is that the visitor ID does almost all the work for a re-fill and almost none of it for a return visit on another device. Ninety seconds apart, the browser is holding the answer. Four days apart on different hardware, it isn’t — and the phone number becomes the workhorse, which is the real argument for collecting one.
The second is that the bottom two rungs are guesses wearing a suit. IP plus user agent inside a tight window is reasonable; the same pair across an afternoon in an office of forty people on one NAT invents relationships that don’t exist. A false merge is worse than a duplicate, because a duplicate is visible and a false merge is not. Bound those tiers by time and volume, and treat what they produce as a suggestion.
This is the ordinary machinery of visitor identity resolution — the same ladder that decides whether returning customers show up as new visitors or as themselves.
How do you decide which email is the real one?
Rank by evidence of intent, not by recency. The most recent address is not automatically the best one; the address attached to the most committed action usually is.
Three rules that hold up in practice.
Keep both. Mark one primary. There is no version of this where deleting an address helps you. Two working inboxes for one prospect is an asset. The only decision you need is which one your sequences send to, and that decision is reversible when one bounces.
Prefer the address attached to the deeper action. An address typed on the step where she picked a demo time beats one typed on a download gate she abandoned. Recency is a decent tiebreaker and a bad primary rule.
Know your motion. In B2B the work domain is usually the real one even when the personal address came second: it identifies the company and it is the one on the contract. In consumer sales the opposite holds — the personal inbox is the one that gets opened.
One special case that isn’t really a second address at all: dotted and plus-tagged Gmail variants. ava.chen@gmail.com, avachen@gmail.com and avachen+shop@gmail.com reach one inbox. Normalising those before you compare is free, and worth doing before you conclude anything about duplicates. Note the asymmetry, though — that rule is Gmail’s, not a universal one, and applying plus-stripping to every domain will merge people who are genuinely distinct.
// Normalise before comparing. Gmail-specific rules only for Gmail.
function normaliseEmail(raw) {
const [user, domain] = raw.trim().toLowerCase().split("@");
if (domain === "gmail.com" || domain === "googlemail.com") {
return `${user.split("+")[0].replace(/\./g, "")}@gmail.com`;
}
return `${user.split("+")[0]}@${domain}`; // plus-tags are common; dots are not safe to strip
}
What can you do about this without buying anything?
Four fixes you can ship yourself, each with the limit that comes with it.
Deduplicate on normalised phone in your CRM. This is the highest-yield move available to most teams, because the phone number is the identifier least likely to change between two fills. The limit is that it only works if you actually collect a phone number and normalise both sides to E.164 before comparing — 0412 345 678, +61412345678 and (04) 1234-5678 are one number that three string comparisons will call three.
Put your own visitor ID in a hidden form field. Generate an ID on first visit, store it, submit it with every form, and both records carry the same value. The limit is durability: an ID written to storage by JavaScript is exactly what browser privacy controls are designed to clear, so the field is reliable for a re-fill and unreliable a week later.
Normalise email before comparing. Lowercase, trim, handle the Gmail variants. The limit is that this catches variants of one address, not two genuinely different mailboxes — the smallest slice of the problem, routinely mistaken for the whole thing.
Ask. “Is this the best address to reach you?” on the confirmation step resolves the ambiguity at the source. The limit is obvious: it only works on submissions that complete, and the records you are trying to join are frequently ones that didn’t.
Notice what all four have in common. They work on data you already hold, which means they cannot help you at all with a fill that was never submitted — and an abandoned first attempt is where the ad click usually lives. That is the gap worth closing.
How does PartialLeads handle a lead who fills the form twice with different emails?
It records both attempts as they are typed, then resolves them to one person on whichever identifier survived — and shows you which one did the joining.
Capture comes first, because you cannot join a record you never made. The tag reads each field on input and blur with a short debounce and flushes anything pending when the page is hidden, so an abandoned first attempt still produces a partial lead with the email, the phone and the click ID on it. That is the whole point of partial lead capture here: the record that carries Ava’s fbclid is the one she never submitted.
Resolution then runs across six tiers — visitor ID, email, phone, IP plus user agent, device fingerprint, and click ID — and unions the sessions that any of them connect. When the email differs, the other five still apply. Three details make that hold up in practice:
- The visitor ID is set server-side as a first-party cookie, with local storage as backup and a one-year lifetime. It is not written by
document.cookiefrom a script, which is what Safari’s seven-day cap targets. A re-fill in the same browser rejoins on this tier whatever the person typed in the email field. - Phone numbers are normalised to E.164 automatically, with the country code inferred from the session’s geo when the visitor didn’t supply one. The number typed three ways is one number before the comparison happens, which is what makes the phone tier usable rather than theoretical.
- The fingerprint tier is deliberately bounded — a ninety-day window, capped at 200 rows — because the point of that rung is to catch a returning device, not to sweep a shared machine into one giant person.
When the purchase arrives it matches on its own tiered ladder — visitor ID echo first, then email, then phone, then IP — so a payment made under the second address still lands on the cluster holding the first. That is the mechanism behind tracking a lead across devices, applied to revenue rather than the lead.
Where you see it working. The Journey column on the Leads list renders each touch as a badge, so a multi-session person carries an “N SESSIONS” label you can read without opening the record. The ring and dash treatment on each badge encodes how that session was stitched into the cluster — visitor ID, email, phone, IP plus user agent, fingerprint or click ID — as a separate visual layer from the channel glyph. That is the specific answer to “are these two rows the same person, and why do you think so?”. The Attribution report then shows first touch, last touch and the resolved model side by side, so you can see what last-click was about to cost you before you act on it.

Three honest constraints, because this is where vendors usually overclaim:
Two fills are still two lead records with two addresses. Resolution decides they belong to one person; it does not delete a row for you, and it does not choose your primary contact address. That judgement stays yours, and the rules earlier in this article are how you make it.
Deduplication of dispatched conversions is per record, not per person. The event_id is derived deterministically and the dedup tables are keyed unique on it, so a redelivered webhook or a retry physically cannot fire the same conversion twice. Two distinct lead records are two distinct records — if your send rules fire on both, the platform receives two events. Controlling that is a question of which rule you fire on, not of deduplication.
And the weak tiers stay weak. IP plus user agent is a low-confidence signal by construction, and it degrades further behind VPNs, corporate networks and private relay services. It is a tiebreaker, never a proof.
| What breaks | The mechanism | Where you see it in the dashboard |
|---|---|---|
| A second email creates a second lead row | Six-tier cluster resolution unions the sessions on visitor ID, phone, click ID, IP + UA or fingerprint | Leads list Journey column: “N SESSIONS” with ring and dash treatment showing which tier joined each one |
| The abandoned first attempt was never recorded, so there is nothing to join | Field capture on input and blur, flushed before the page is hidden | Leads page, Partial badge on the record she never submitted |
| The same-browser join dies when Safari caps the cookie | Visitor ID set server-side as a first-party cookie, one-year lifetime, local storage backup | The later row continues the existing journey instead of starting a new one |
| The phone number was typed differently each time | E.164 normalisation with country code inferred from session geo | Phone column on the Leads list |
| The purchase arrives under the second address | Purchase matched tiered: visitor ID echo, then email, then phone, then IP | Revenue on the lead, Purchases ledger |
| Credit splits between the ad click and “Direct” | First-touch and last-touch rows written per matched purchase, plus the resolved model | Attribution report, three models side by side |
| A redelivered webhook re-fires the conversion | Deterministic event_id plus unique-keyed dedup tables |
CAPI activity log, and the per-lead API column |
The short version: stop cleaning duplicates and start resolving identity. A duplicate is a symptom of a join you didn’t make, and the row you were going to delete is usually the one holding the click.
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.