Lead Matching
Match sales leads across Salesforce, HubSpot, and LinkedIn Sales Navigator to eliminate duplicates, consolidate lead intelligence, and route the richest profile to your sales team.
The Problem
Your sales team uses three lead sources: inbound leads captured in HubSpot from marketing campaigns, outbound prospects in Salesforce from the SDR team, and enrichment data from LinkedIn Sales Navigator. The same prospect often enters all three systems:
- A prospect fills out a form on your website (HubSpot) and an SDR independently reaches out on LinkedIn (Salesforce). Neither team knows the other has made contact.
- LinkedIn enrichment data uses the prospect's full legal name ("Robert James Smith"), while the CRM has the name they signed up with ("Bob Smith").
- Company names vary between systems: "Acme Corp" in the CRM, "Acme Corporation" in LinkedIn, "ACME" in the marketing form.
Without deduplication, your SDR sends a cold email to someone who is already an active marketing lead. The prospect sees two outreach emails from the same company and loses trust.
The Data
Salesforce (Outbound Prospects)
| lead_id | name | company | title | phone | lead_source | created_date | |
|---|---|---|---|---|---|---|---|
sf_lead_001 | Bob Smith | [email protected] | Acme Corp | VP Engineering | +1-555-0201 | outbound | 2026-01-08 |
sf_lead_002 | Jenny Lee | [email protected] | Globex | Product Manager | null | outbound | 2026-01-10 |
sf_lead_003 | Michael Chen | [email protected] | Initech | CTO | +1-555-0203 | referral | 2026-01-12 |
sf_lead_004 | Sarah Connor | [email protected] | Cyberdyne Systems | Director of Ops | +1-555-0204 | outbound | 2026-01-14 |
sf_lead_005 | Dave Wilson | [email protected] | Umbrella Corp | Head of Data | +1-555-0205 | inbound | 2026-01-15 |
sf_lead_006 | Amy Zhang | [email protected] | Soylent Corp | Data Engineer | null | outbound | 2026-01-16 |
sf_lead_007 | Tom Harris | [email protected] | Wayne Enterprises | VP Sales | +1-555-0207 | referral | 2026-01-17 |
HubSpot (Inbound Marketing Leads)
| contact_id | full_name | email_address | company_name | job_title | phone_number | lead_score | form_submitted |
|---|---|---|---|---|---|---|---|
hub_44201 | Robert Smith | [email protected] | Acme Corporation | VP of Engineering | null | 85 | demo_request |
hub_44202 | Jennifer Lee | [email protected] | Globex Corporation | PM | +1-555-0302 | 72 | whitepaper |
hub_44203 | Mike Chen | [email protected] | Initech Inc | Chief Technology Officer | +1-555-0203 | 91 | demo_request |
hub_44204 | S. Connor | [email protected] | Cyberdyne | Dir. Operations | +1-555-0204 | 45 | newsletter |
hub_44205 | Lisa Park | [email protected] | Oscorp Industries | Data Analyst | +1-555-0305 | 68 | whitepaper |
hub_44206 | Amy Z. | [email protected] | Soylent | null | +1-555-0306 | 34 | newsletter |
hub_44207 | David Wilson | [email protected] | Umbrella Corporation | Head of Data Engineering | +1-555-0205 | 77 | case_study |
LinkedIn Sales Navigator (Enrichment)
| profile_id | display_name | work_email | organization | headline | location | connections |
|---|---|---|---|---|---|---|
li_prof_901 | Robert James Smith | [email protected] | ACME | VP Engineering at ACME | San Francisco, CA | 1247 |
li_prof_902 | Jennifer Lee | [email protected] | Globex Corporation | Product Manager at Globex | New York, NY | 832 |
li_prof_903 | Michael J. Chen | [email protected] | Initech, Inc. | CTO at Initech | Austin, TX | 2103 |
li_prof_904 | Sarah J. Connor | [email protected] | Cyberdyne Systems Inc | Director of Operations | Los Angeles, CA | 654 |
li_prof_905 | Thomas Harris | [email protected] | Wayne Enterprises | VP Sales at Wayne Enterprises | Chicago, IL | 1876 |
li_prof_906 | Patricia Nguyen | [email protected] | Stark Industries | Head of Analytics | Seattle, WA | 943 |
Data Challenges by Prospect
| Prospect | Salesforce | HubSpot | Challenge | |
|---|---|---|---|---|
| Bob/Robert Smith | [email protected] | [email protected] | [email protected] | 3 different emails, name as "Bob" / "Robert" / "Robert James" |
| Jenny/Jennifer Lee | [email protected] | [email protected] | [email protected] | 3 different emails, "Jenny" / "Jennifer" / "Jennifer" |
| Michael Chen | [email protected] | [email protected] | [email protected] | 3 different emails, "Michael" / "Mike" / "Michael J." |
| Sarah Connor | [email protected] | [email protected] | [email protected] | 3 different emails, "Sarah" / "S." / "Sarah J." |
| Dave/David Wilson | [email protected] | [email protected] | not in LinkedIn | 2 different emails, "Dave" / "David" |
| Amy Zhang | [email protected] | [email protected] | not in LinkedIn | 2 different emails, name truncated in HubSpot |
| Tom Harris | [email protected] | not in HubSpot | [email protected] | 2 different emails, "Tom" / "Thomas" |
| Lisa Park | not in Salesforce | [email protected] | not in LinkedIn | Only in HubSpot (singleton) |
| Patricia Nguyen | not in Salesforce | not in HubSpot | [email protected] | Only in LinkedIn (singleton) |
Not a single prospect has the same email address across all their sources. Exact email matching alone would fail to deduplicate any of these leads.
The Spec
api_version: kanoniv/v1
identity_version: "1.0"
entity:
name: lead
description: "Sales lead deduplication across CRM, marketing, and enrichment platforms"
sources:
- name: salesforce
adapter: csv
location: data/salesforce_leads.csv
primary_key: lead_id
attributes:
name: name
email: email
company: company
title: title
phone: phone
lead_source: lead_source
created_date: created_date
- name: hubspot
adapter: csv
location: data/hubspot_contacts.csv
primary_key: contact_id
attributes:
name: full_name
email: email_address
company: company_name
title: job_title
phone: phone_number
lead_score: lead_score
form_submitted: form_submitted
- name: linkedin
adapter: csv
location: data/linkedin_navigator.csv
primary_key: profile_id
attributes:
name: display_name
email: work_email
company: organization
title: headline
location: location
connections: connections
rules:
# Email exact match. Despite different email addresses per person,
# some pairs may share an email (e.g., future data loads).
# Weight is highest because email is the most reliable identifier.
- name: email_exact
type: exact
field: email
weight: 1.0
# Fuzzy name matching is critical here because every prospect
# has name variations. Jaro-Winkler at 0.88 catches:
# "Bob Smith" vs "Robert Smith" -> 0.78 (MISS -- below threshold)
# "Jennifer Lee" vs "Jenny Lee" -> 0.89 (HIT)
# "Michael Chen" vs "Mike Chen" -> 0.86 (MISS -- just below)
# "Michael Chen" vs "Michael J. Chen" -> 0.93 (HIT)
# Some pairs rely on corroborating rules to bridge the gap.
- name: name_fuzzy
type: similarity
field: name
algorithm: jaro_winkler
threshold: 0.88
weight: 0.6
# Company name fuzzy match corroborates name matches.
# "Acme Corp" vs "Acme Corporation" -> 0.92 JW (HIT)
# "Acme Corp" vs "ACME" -> 0.76 JW (MISS)
# "Globex" vs "Globex Corporation" -> 0.85 JW (MISS)
# "Initech" vs "Initech Inc" -> 0.91 JW (HIT)
- name: company_fuzzy
type: similarity
field: company
algorithm: jaro_winkler
threshold: 0.90
weight: 0.3
# Phone exact match as a strong corroborating signal.
# Bridges records when email and name both differ.
- name: phone_exact
type: exact
field: phone
weight: 0.8
blocking:
strategy: phonetic
keys: [name]
decision:
scoring: weighted_sum
thresholds:
match: 0.85
review: 0.55
review_queue:
enabled: true
sla_hours: 336 # 14 days
survivorship:
default: most_complete
overrides:
- field: email
strategy: source_priority
priority: [salesforce, hubspot, linkedin]
- field: company
strategy: source_priority
priority: [linkedin, salesforce, hubspot]
- field: name
strategy: source_priority
priority: [linkedin, salesforce, hubspot]
metadata:
owner: [email protected]
tags: [lead-matching, sales, dedup]
description: "Cross-platform lead deduplication with review queue"How the Rules Score Each Prospect
Bob/Robert Smith (Acme)
| Pair | email_exact | name_fuzzy | company_fuzzy | phone_exact | Total | Decision |
|---|---|---|---|---|---|---|
| SF / HubSpot | 0.0 | 0.0 (0.78 JW) | 0.3 (0.92 JW) | 0.0 (null) | 0.3 | Reject |
| SF / LinkedIn | 0.0 | 0.0 (0.72 JW) | 0.0 (0.76 JW) | 0.0 (no field) | 0.0 | Reject |
| HubSpot / LinkedIn | 0.0 | 0.6 (0.91 JW) | 0.0 (0.76 JW) | 0.0 (no field) | 0.6 | Review |
"Bob Smith" vs "Robert Smith" fails the name threshold. But "Robert Smith" vs "Robert James Smith" scores 0.91. The HubSpot-LinkedIn pair enters the review queue at 0.6, and a human reviewer can confirm the match and pull in the Salesforce record transitively.
Jenny/Jennifer Lee (Globex)
| Pair | email_exact | name_fuzzy | company_fuzzy | phone_exact | Total | Decision |
|---|---|---|---|---|---|---|
| SF / HubSpot | 0.0 | 0.6 (0.89 JW) | 0.0 (0.85 JW) | 0.0 (null) | 0.6 | Review |
| SF / LinkedIn | 0.0 | 0.6 (0.89 JW) | 0.3 (0.92 JW) | 0.0 (no field) | 0.9 | Match |
| HubSpot / LinkedIn | 0.0 | 0.6 (0.95 JW) | 0.3 (0.92 JW) | 0.0 (no field) | 0.9 | Match |
"Jenny Lee" vs "Jennifer Lee" scores 0.89 in Jaro-Winkler. LinkedIn and both CRM sources match. All three records cluster together.
Michael Chen (Initech)
| Pair | email_exact | name_fuzzy | company_fuzzy | phone_exact | Total | Decision |
|---|---|---|---|---|---|---|
| SF / HubSpot | 0.0 | 0.0 (0.86 JW) | 0.3 (0.91 JW) | 0.8 | 1.1 | Match |
| SF / LinkedIn | 0.0 | 0.6 (0.93 JW) | 0.3 (0.91 JW) | 0.0 (no field) | 0.9 | Match |
| HubSpot / LinkedIn | 0.0 | 0.0 (0.82 JW) | 0.3 (0.91 JW) | 0.0 (no field) | 0.3 | Reject |
Phone match bridges SF-HubSpot despite "Michael Chen" vs "Mike Chen" being below the name threshold. SF-LinkedIn matches via "Michael Chen" vs "Michael J. Chen". HubSpot-LinkedIn ("Mike Chen" vs "Michael J. Chen") rejects, but transitive closure through Salesforce clusters all three.
Sarah Connor (Cyberdyne)
| Pair | email_exact | name_fuzzy | company_fuzzy | phone_exact | Total | Decision |
|---|---|---|---|---|---|---|
| SF / HubSpot | 0.0 | 0.0 (0.71 JW) | 0.0 (0.87 JW) | 0.8 | 0.8 | Review |
| SF / LinkedIn | 0.0 | 0.6 (0.92 JW) | 0.3 (0.95 JW) | 0.0 (no field) | 0.9 | Match |
| HubSpot / LinkedIn | 0.0 | 0.0 (0.68 JW) | 0.0 (0.87 JW) | 0.0 (no field) | 0.0 | Reject |
"Sarah Connor" vs "S. Connor" scores only 0.71. But phone bridges SF-HubSpot into review, and SF-LinkedIn matches cleanly. The review queue catches the HubSpot record.
The Result
Match Summary
Sources: 3
Total records: 20
Clusters: 9
- 3-record clusters: 3 (Jennifer Lee, Michael Chen, Sarah Connor)
- 2-record clusters: 3 (Dave Wilson, Amy Zhang, Tom Harris)
- Singletons: 3 (Lisa Park, Patricia Nguyen, Bob Smith partially)
Match pairs: 7
Review queue: 4
Rejected pairs: 9Matched Leads (Automatic)
| Cluster | Lead | Sources | Confidence | Match Path |
|---|---|---|---|---|
| 1 | Jennifer Lee | SF + HubSpot + LinkedIn | 0.90 | name_fuzzy + company_fuzzy |
| 2 | Michael Chen | SF + HubSpot + LinkedIn | 0.93 | phone_exact (SF-HB) + name_fuzzy (SF-LI) |
| 3 | Sarah Connor | SF + LinkedIn | 0.90 | name_fuzzy + company_fuzzy |
| 4 | Dave Wilson | SF + HubSpot | 0.86 | phone_exact + company_fuzzy |
| 5 | Tom Harris | SF + LinkedIn | 0.88 | name_fuzzy + company_fuzzy |
Review Queue
| Candidate Pair | Score | Reason for Review | Signal |
|---|---|---|---|
| Bob Smith (SF) / Robert Smith (HubSpot) | 0.60 | company_fuzzy only | Same company, name below threshold |
| Robert Smith (HubSpot) / Robert James Smith (LinkedIn) | 0.60 | name_fuzzy only | Name match, company below threshold |
| Sarah Connor (SF) / S. Connor (HubSpot) | 0.80 | phone_exact only | Phone match, name abbreviated |
| Amy Zhang (SF) / Amy Z. (HubSpot) | 0.56 | phone_exact only | Phone match, name truncated |
Consolidated Lead View (Jennifer Lee Example)
After survivorship with most_complete strategy and field overrides:
{
"canonical_id": "canon_lead_002",
"entity_type": "lead",
"canonical_data": {
"name": "Jennifer Lee",
"email": "[email protected]",
"company": "Globex Corporation",
"title": "Product Manager",
"phone": "+1-555-0302",
"location": "New York, NY",
"lead_score": 72,
"connections": 832,
"lead_source": "outbound",
"form_submitted": "whitepaper"
},
"field_provenance": {
"name": { "source": "linkedin", "strategy": "source_priority" },
"email": { "source": "salesforce", "strategy": "source_priority" },
"company": { "source": "linkedin", "strategy": "source_priority" },
"title": { "source": "hubspot", "strategy": "most_complete" },
"phone": { "source": "hubspot", "strategy": "most_complete" },
"location": { "source": "linkedin", "strategy": "most_complete" },
"lead_score": { "source": "hubspot", "strategy": "most_complete" },
"connections": { "source": "linkedin", "strategy": "most_complete" },
"lead_source": { "source": "salesforce", "strategy": "most_complete" },
"form_submitted": { "source": "hubspot", "strategy": "most_complete" }
},
"cluster": {
"size": 3,
"sources": ["salesforce", "hubspot", "linkedin"],
"confidence": 0.90
}
}The golden record combines the best data from each source: LinkedIn's authoritative name and company, Salesforce's email for outreach, HubSpot's engagement data, and LinkedIn's enrichment metadata.
Key Takeaway
Fuzzy matching plus a review queue catches matches that exact matching misses, while keeping humans in the loop for uncertain cases. In this dataset, zero email addresses match exactly across sources. Without fuzzy name and company matching, every single lead would remain a duplicate. The review queue with a low threshold (0.55) ensures borderline cases like "Bob" vs "Robert" are flagged for human decision rather than silently missed or incorrectly auto-merged.
The most_complete survivorship strategy is ideal for lead matching because you want the richest possible profile, with every field filled in from whichever source has it.
Variations to Consider
If you want to catch "Bob" vs "Robert" automatically, use Soundex or Metaphone:
- name: name_phonetic
type: similarity
field: name
algorithm: soundex
threshold: 1.0
weight: 0.4Soundex maps "Robert" and "Bob" differently (R163 vs B100), so this would not help for that specific case. For nickname resolution, consider a preprocessing step that normalizes common nicknames before matching.
If you need stricter matching (e.g., B2B where false positives are costly):
decision:
thresholds:
match: 0.95 # very high bar for auto-match
review: 0.70 # send more to reviewIf you want to score by lead engagement, use an aggregate survivorship override:
survivorship:
default: most_complete
overrides:
- field: lead_score
strategy: aggregate
function: maxRelated
- Spec Reference: Rules: Algorithm comparison for name matching
- Spec Reference: Survivorship:
most_completestrategy details - Tuning Match Quality: Diagnose and improve match quality
- Tutorial: Lead Matching: Step-by-step walkthrough with Python SDK
