listen · lead scoring and merging
Identity resolution
Merging one person across sources.
In Lead scoring and merging
How signals about one human, scattered across X, Reddit, Threads, your site, your waitlist and Stripe, become one person with one history, without ever joining two people who only look alike.
signal → lead → identity resolution → playbook
Hard keys link, soft matches ask — Only a key one person can own joins automatically. Everything else waits for a second rule or the founder.
Links are the source of truth — A person is rebuilt from accepted links, so a wrong merge is undone by splitting one link.
Every split is a label — The founder's corrections are the best training data the resolver will ever get.
what it is, in industry terms
One person, however many places they show up.
Every signal arrives as a claim about a handle, an email or a domain. Identity resolution turns those claims into people, so a lead is about a human and not about an account. It's the first thing the Lead stage does, before any score is computed.
Entity resolution — also: identity resolution, record linkage, dedupe · Deciding which records describe the same real-world person, across sources that share no common id.
Identity claim — a handle, email, domain or profile link · One way a person shows up: @yourname on X, the same name on Reddit, you@example.com on the waitlist.
Deterministic match — hard-key join · Two claims share a key that can only belong to one person: the same platform user id, the same normalized email.
Probabilistic match — fuzzy match, scored · Two claims look alike (name, product words, phrasing) and get a confidence, never a certainty.
Blocking — candidate generation · Only compare records that share something cheap (a domain, a product URL, a name token), so the pair count stays small.
Cluster — the person · All claims joined by accepted links. Built with union-find, so one split re-forms it.
how it works
Seven steps from claims to a person.
Collect claims
every signal and enrichment adds handles, emails, domains and bio links
Normalize
platform user ids over display names; lowercase; strip plus-addressing; resolve redirects
Block
pair only claims that share a domain, product URL, name token or bio link
Match
deterministic rules link with certainty; probabilistic rules return a confidence
Decide
above the auto bar: link. In between: ask the founder. Below: keep apart
Record the link
rule id, rule version, confidence, evidence URL, who decided
Rebuild the person
union-find over accepted links; a founder split removes one link and re-forms both people
- Collect claims — every signal and enrichment adds handles, emails, domains and bio links
- Normalize — platform user ids over display names; lowercase; strip plus-addressing; resolve redirects
- Block — pair only claims that share a domain, product URL, name token or bio link
- Match — deterministic rules link with certainty; probabilistic rules return a confidence
- Decide — above the auto bar: link. In between: ask the founder. Below: keep apart
- Record the link — rule id, rule version, confidence, evidence URL, who decided
- Rebuild the person — union-find over accepted links; a founder split removes one link and re-forms both people
the asymmetry — A wrong merge is worse than a missed one. A missed merge costs a duplicate lead the contact policy can still catch; a wrong merge sends one person's context, history and tone to someone else. So the auto-link bar is high, and soft matches wait for a human.
the objects
A link between two claims, and the person they make.
Links are the unit of truth. People are rebuilt from links, so undoing a mistake is one row, not a migration.
identity_link
| Field | Type | What it holds |
|---|---|---|
| link_id | uuid | Stable id for this one join. |
| person_id | uuid | The cluster the link belongs to after it is accepted. |
| a, b | claim | The two identity claims: channel, value as seen, normalized value, platform user id when there is one. |
| rule_id | string | Which rule proposed it, e.g. bio-cross-link. |
| rule_version | int | Rules change; old links keep the version that made them. |
| method | deterministic | probabilistic | manual | How the join was made. |
| confidence | 0 to 1 | 1.0 for hard keys and founder merges; a score for fuzzy rules. |
| evidence | list of URLs | Where the join can be seen: the bio, the listing, the sign-up. |
| decided_by | system | founder | Who accepted it. |
| status | active | split | A split link stays in the table as a label; it just stops joining. |
| created_at, split_at | timestamps | When it was made and, if split, when. |
| split_reason | text | Why the founder split it: cofounder, shared agency domain, wrong person. |
person
| Field | Type | What it holds |
|---|---|---|
| person_id | uuid | One human. Several leads over time can point here. |
| display_name | string | Best current name, from the highest-confidence claim. |
| claims | list | Every handle, email and domain in the cluster, each with the link that brought it in. |
| company | ref, optional | Shared domain or product. Two cofounders are two people, one company. |
| never_contact | bool + reason | Lives on the person, not the lead, so it follows every handle. |
sample, as data
{
"link_id": "lk_4e21",
"person_id": "pn_19b0e4",
"a": {
"channel": "x",
"value": "@example_founder",
"platform_user_id": "1520…"
},
"b": {
"channel": "reddit",
},
"rule_id": "bio-cross-link",
"rule_version": 2,
"method": "deterministic",
"confidence": 0.97,
"evidence": [
],
"decided_by": "system",
"status": "active",
"created_at": "2026-10-16T21:41Z",
"split_at": null,
"split_reason": null
}the rule catalog
Fourteen join rules, from certain to suggestive.
Deterministic rules can auto-link. Probabilistic rules only suggest, unless a second rule agrees. The guard stops runaway chains.
rule.same-platform-id — deterministic
Same handle, same channel
- joins when
- the platform's user id matches, even after a rename
- confidence
- certain
- watch out
- keying on the display text: renames break it
rule.same-email — deterministic
Same email
- joins when
- normalized email matches across waitlist, Stripe and newsletter
- confidence
- certain
- watch out
- plus-addressing and Gmail dots; normalize first
rule.stripe-customer — deterministic
Stripe customer email
- joins when
- a paying customer's email matches a lead or waitlist email
- confidence
- certain
- watch out
- a company card paid by someone else: the payer is not the user
rule.same-domain-founder — deterministic
Same domain, founder role
- joins when
- a bio links a domain that matches a sign-up's email domain or a launch listing
- confidence
- high
- watch out
- shared domains: gmail.com, agencies, studios, universities
rule.bio-cross-link — deterministic
Bio cross-link
- joins when
- an X or Threads bio links the Reddit, GitHub or site profile, or the reverse
- confidence
- high
- watch out
- links to a product they work on but don't own
rule.listing-maker — deterministic
Launch listing maker
- joins when
- the maker named on a launch listing links the same handle or domain
- confidence
- high
- watch out
- listings posted by an agency for a client
rule.same-product-two-handles — probabilistic
Same product, two handles
- joins when
- two handles both claim the same product URL as their own
- confidence
- medium
- watch out
- cofounders: link them to one company, keep two people
rule.avatar-hash — probabilistic
Same avatar image
- joins when
- profile pictures hash to the same image on two platforms
- confidence
- medium
- watch out
- default avatars, logos used by a whole team
rule.name-product — probabilistic
Name plus product words
- joins when
- similar display names and the same distinctive product words within 30 days
- confidence
- low to medium
- watch out
- common names; never auto-merge, ask
rule.same-username — probabilistic
Same username, two platforms
- confidence
- low to medium
- watch out
- squatted or common usernames; needs a second piece of evidence
rule.writing-match — probabilistic
Writing match
- joins when
- the same unusual phrases and the same pain, on two platforms, close in time
- confidence
- low
- watch out
- coincidence and quoting; use only to suggest
rule.founder-merge — manual
Founder merge
- joins when
- the founder says two records are one person
- confidence
- certain
- watch out
- keep it as a positive label for the resolver
rule.founder-split — manual
Founder split
- joins when
- the founder says a merge was wrong
- confidence
- certain
- watch out
- the most valuable label here: it teaches what not to join
rule.cluster-cap — guard
Cluster size guard
- joins when
- a cluster passes 6 claims or 2 product domains
- confidence
- stops auto-linking
- watch out
- chains: A looks like B, B like C, and suddenly five people are one
failure modes
Six ways merging goes wrong, and how to catch each.
A wrong merge — Two cofounders on one product domain become one person; one of them gets messages meant for the other. · catch itlink to a shared company, not a shared person, whenever two handles claim the same product
A missed merge — The same founder is a lead on Reddit and on X and gets two first messages in one week. · catch itthe contact policy checks the person, not the lead; count duplicates the founder flags
Chain merges — Weak links stack until one cluster holds several people. · catch itcap cluster size; above it, every new link needs the founder
Renamed handles — Keying on @text breaks when someone renames, and a stranger takes the old name. · catch itstore the platform user id on every claim
Shared domains — gmail.com, agency and university domains join strangers. · catch ita blocklist of shared domains; domain rules apply to custom domains only
Private data — Merging on data the person didn't make public breaks trust and platform terms. · catch itonly public profiles and first-party data (your waitlist, your Stripe)
data to capture
Every link decision is a training row.
Following the guide's data rules: log every decision, version the rule, keep the rejected pairs, and treat each founder split as gold.
record
- every proposed link, accepted or not, with the pair's features, rule id and version, confidence and decision
- every founder merge and split, with the reason
identity_linkedandidentity_unlinkedevents (GTM tracking plan)- duplicate-contact incidents: a person touched twice through two un-merged leads
label
- founder split: this link was wrong (negative)
- founder merge: these were one person (positive)
- untouched auto-links after 30 days: weak positives
trains
- a pairwise matcher (logistic regression or gradient boosting on pair features) that replaces hand-set confidences
- a calibrated auto-link threshold: the bar where measured precision clears 99%
test
- replay a new rule set offline on stored pairs and score it against founder labels before it links anyone
- track auto-link precision and the founder review queue size per week
Handles, products and scores in examples are invented. Merge only on public or first-party data, keep never-contact sticky, and a person always sends the first message.