TL;DR: When two CRM records share a name, name matching cannot tell you whether they are the same company entered twice or two genuinely different companies. The only reliable tiebreaker is physical location. We geocode both addresses, measure the distance between them, corroborate with phone and address signals, and only then decide whether to merge. Running this on 570 ambiguous pairs, 76% turned out to be legitimately separate companies that a naive merge would have destroyed. Here is the full pipeline.
Every CRM deduplication project eventually hits the same wall.
You have two records — both named Summit Fabrication, both in Ohio, both with real contacts and open deals attached. Your name-matching algorithm flags them as likely duplicates. But are they?
One might be a data-entry mistake: the same Cleveland plant entered twice by two different reps. Or they might be two genuinely different facilities — one in Cleveland, one in Akron — that happen to share a common name. Name matching cannot tell them apart. The state field cannot either. Even the city field is unreliable, because it gets mistyped, abbreviated, or left blank.
The one thing that can tell them apart is the physical address. And the only way to resolve a physical address reliably at scale is to geocode it.
This is the story of the pipeline we built to do exactly that — the pass we call B5 geocoding — and how it resolved 570 address-ambiguous duplicate suspects inside a HubSpot CRM holding more than a million company records.
Why name matching runs out of road
Our deduplication engine sorts every company record into one of five buckets based on the strength of the evidence available:
- B1 — True singletons. No duplicate suspect at any confidence level. No action needed.
- B2 / B3 — Exact duplicates. Same normalised name, same city, same state. High confidence — merge them.
- B4 — Keyword-resolved. Same core tokens in the name, phrased differently. Resolved with fuzzy scoring.
- B5 — Address-ambiguous. Same name across multiple cities or states. Name matching is exhausted. Geocoding required.
By the time a pair reaches B5, the algorithm has already normalised both names (stripping filler tokens like inc, llc, corp, manufacturing, and industries), scored their token sets with Jaccard similarity, and compared city and state. It is confident the names are related. It is not confident the companies are the same.
The core problem: The algorithm knows the names match. It does not know if the buildings do.
B5 pairs split into two conflict types:
SAME_STATE_DIFF_CITY— same name, same state, different cities. Could be a city-field typo, or a genuinely separate plant across the state.DIFF_STATE— same name across state lines. Almost always different companies, but occasionally the same national organisation with a wrong state on one record.
The geocoding pipeline, step by step
For every B5 pair, we run the same five-step sequence.
1. Pull both records from the CRM
Fetch street address, city, state, phone, and name for each company ID through the HubSpot CRM v3 API. Everything downstream depends on the raw address fields being retrieved cleanly.
2. Geocode each address
Pass the full address string for each record to the Google Maps Geocoding API. Capture the returned latitude, longitude, and formatted address. If the API returns no usable match — no result, or a permanently closed flag — mark the record UNABLE_TO_VERIFY and stop.
3. Compute the Haversine distance
Calculate the great-circle distance in miles between the two coordinate pairs. This is the primary signal. Two addresses that geocode to within 0.1 miles are almost certainly the same building.
4. Score the pair
Combine distance with three secondary signals — phone match, address-string similarity, and name similarity — into a single 0–100 composite score. (Full model below.)
5. Classify and act
- Score ≥ 85 →
DUPLICATE— merge candidate. - Score 50–84 → review — human review queue.
- Score < 50 →
SEPARATE_LOCATION— keep both records.
The scoring model
Distance is the dominant signal, but deliberately not the only one.
| Signal | Max pts | Logic |
|---|---|---|
| Distance | 40 | < 0.1 mi = 40 · < 0.5 mi = 30 · < 2 mi = 15 · > 2 mi = 0 |
| Address string match | 30 | Normalised token overlap between raw address fields |
| Phone match | 20 | Exact match on E.164-normalised number = 20 · partial (area code) = 8 |
| Name similarity | 10 | Jaccard on normalised token sets |
| Total | 100 | ≥ 85 → DUPLICATE · 50–84 → review · < 50 → SEPARATE_LOCATION |
Why distance is capped at 40 points
A pair that geocodes to within 0.1 miles scores 40 on distance — and that alone is not enough to trigger a merge. We require at least one corroborating secondary signal (a matching phone number, a matching address string, or very high name similarity) before promoting a pair to DUPLICATE.
The reason is simple: geocoding errors are real. The Maps API will occasionally snap an address to a nearby landmark, intersection, or building centroid rather than the exact unit. Requiring a second, independent signal keeps a single bad geocode from causing a bad merge.
⚠ Watch out for this — one building, many companies. Two records at the same address are not automatically duplicates. An industrial park or multi-tenant facility at a single street address might house three independent contract manufacturers — all geocoding to identical coordinates, all legitimately separate companies. Check the Maps API
place_id: if both records return the same place ID and POI name, that is strong confirmation they are the same entity. If the place IDs differ, keep the records separate even when the coordinates match.
What the results actually looked like
We ran 570 company records — every B5 suspect with at least one associated contact — through the pipeline. Six analysts worked the review queue, using the geocode output and Maps API notes to make the final call on each.
| Outcome | Count | Meaning |
|---|---|---|
| Processed | 570 | B5 companies reviewed with the Maps API |
SEPARATE_LOCATION | 436 | Confirmed different orgs — keep both records |
DUPLICATE | 22 | Same physical location — merge candidates |
UNABLE_TO_VERIFY | 110 | Insufficient public data — deferred |
The 76% SEPARATE_LOCATION rate is the most important number here. It means the vast majority of B5 suspects were correctly-entered, legitimate companies that simply share a common name. Without geocoding, a naive merge would have flattened those records — consolidating contacts, deals, and history from two unrelated plants into one wrong record, with no undo.
What SEPARATE_LOCATION actually means
This is the term that trips people up most, so it is worth being precise.
A SEPARATE_LOCATION result does not assert that the two records are definitely different companies. It asserts that the geocoded addresses are physically far apart — and therefore geography alone cannot confirm a match. In practice, most SEPARATE_LOCATION results genuinely are different companies with the same name. "Precision Metal Works" exists in dozens of states; geocoding two of them to cities 800 miles apart simply confirms what common sense already suggested.
Key distinction:
SEPARATE_LOCATIONis a classification outcome, not a merge decision. It means "geography did not confirm a match." The correct action is to keep both records. The incorrect action is to assume something must be wrong with one of them.
Handling the 22 confirmed duplicates
Of the 22 records classified DUPLICATE, three distinct sub-types emerged, each needing different handling.
Type A — True same-location entries (6 pairs). Two records pointing to the exact same physical address, created independently by different reps or import jobs. These are clean merges: copy fill-in-the-blank fields from the archive record to the master, then call the merge API.
Type B — Multi-location organisations (10 records). Large companies — an industrial distributor or a fabrication group with several real plants. Each record is a valid, distinct location. The analyst note reads "multiple locations, this is the unique one." These should not be merged. Link them as parent/child in HubSpot instead.
Type C — Ambiguous (6 records). The analyst found some signal of duplication, but the addresses differed enough to leave real doubt. These go to a human review queue rather than an auto-merge.
The merge script — resolve the current master every time
For Type A merges we reused the bulk merge script from the earlier B2/B3 exact-duplicate pass. The critical implementation detail: HubSpot does not keep master record IDs stable across merges. Every merge creates a new primary record and redirects both old IDs to it. (We covered why in our breakdown of what HubSpot merges do to record IDs.)
# Wrong: resolve once at the start, then loop
current_master = resolve(original_master_id)
for archive_id in archives:
merge(current_master, archive_id) # fails from pair 2 onward
# Right: resolve the current master immediately before each merge
seed = original_master_id
for archive_id in archives:
current_master, _ = resolve(seed) # follows the redirect chain
merge(current_master, archive_id)
time.sleep(0.5)
seed = current_master # next resolve starts here
We learned this the hard way merging seven Cedar Valley Fabrication records in sequence. The first merge succeeded. The second through seventh all returned HTTP 400 — cannot merge because this record has a forward reference. The master ID had changed with every merge. Re-resolving the current primary before each individual call fixed it.
What to do with the 110 "unable to verify"
In our dataset, 110 records could not be resolved: the Maps API returned no match, or analysts found multiple conflicting results for the same address. Nearly half of these had blank analyst notes — the reviewer simply got stuck.
Our recommendation for this category is defer, don't guess. Contact density on these records was low (three to four contacts each on average), and the cost of a wrong merge far outweighs the benefit of consolidating a handful of contacts. Mark them reviewed-inconclusive and revisit in a later pass with better source data — a phone call to the company, a website cross-check, or enrichment from a third-party provider.
What we would do differently next time
Three changes would make the next run faster and cleaner.
Add Places API verification alongside the Geocoding API. The Geocoding API resolves an address to coordinates but tells you nothing about what business is there. The Places API returns a place name and place_id. Matching the place ID across two records is far stronger evidence of duplication than coordinate proximity alone.
Pre-filter DIFF_STATE pairs automatically. Records with the same name in different states have a very low duplicate rate — most are legitimately separate companies. Sending them all through geocoding burned analyst time for a near-certain SEPARATE_LOCATION outcome. A simple rule — if states differ and there is no phone match, classify as SEPARATE_LOCATION without geocoding — would have cut the review queue by roughly 30%.
Capture analyst notes as structured fields, not free text. Free-text notes like "same as our address" vs "Same as our address" vs "address matches" created inconsistent downstream parsing. A four-option dropdown (SAME_ADDRESS / DIFF_ADDRESS / SAME_POI / CLOSED) would have made the results far easier to act on programmatically.
Why this step is worth the cost
The B5 geocoding pass is the most expensive step in a CRM deduplication project — in API calls, in analyst time, and in implementation complexity. It is also the most important.
The records that reach B5 are precisely the ones where naive deduplication does the most damage: real contacts at real companies, one keystroke away from being merged simply because two plants happen to share a name. Getting geography right before you merge is not optional. It is the difference between a cleaner CRM and an actively broken one.
Frequently Asked Questions
How do you tell if two CRM records are the same company or two different ones with the same name? When the name, city, and state fields can't settle it, use physical location. Geocode both addresses to latitude/longitude, measure the great-circle (Haversine) distance between them, and corroborate with phone number and address-string matches. Records within ~0.1 miles that also share a phone or address are almost certainly the same company; records geocoding far apart are almost certainly separate.
What is address-ambiguous deduplication? It's the case where two CRM records share a normalised name but appear in different cities or states, so name matching alone can't decide whether they're duplicates. Resolving them requires an external signal — geography — rather than more string comparison.
Can the Google Maps API be used for CRM deduplication?
Yes. The Geocoding API converts each record's address into coordinates you can compare by distance. Pairing it with the Places API adds a place_id and business name, which lets you confirm two records point to the same physical entity rather than just nearby coordinates.
Why not just merge every record with a matching name? Because many companies legitimately share a name across locations — think "Precision Metal Works" in a dozen states. In our 570-pair sample, 76% were separate companies. A blind name-based merge would have destroyed those records, consolidating unrelated contacts, deals, and history with no undo.
Do two records at the same address always mean a duplicate?
No. Multi-tenant facilities and industrial parks can house several independent companies at one street address, all geocoding to identical coordinates. Compare the Maps API place_id and POI name: same place ID means same entity; different place IDs mean keep both records separate.
Why cap the distance signal in the dedup score? Geocoding is imperfect — the API can snap an address to a nearby landmark or intersection. Capping distance so it can't reach the merge threshold on its own forces at least one independent corroborating signal (phone, address, or name), which guards against a single bad geocode triggering a bad merge.
Summary
Name matching gets a deduplication project 90% of the way there. The last 10% — the address-ambiguous pairs — is where it either succeeds or quietly corrupts your CRM. Geography is the only reliable tiebreaker: geocode both addresses, measure the distance, corroborate with a second signal, and let the score decide. Merge only what you can confirm; keep everything you can't.
Running duplicate records at scale in HubSpot? CleanupCRM automates the identification, scoring, and safe merging of duplicates — including the address-ambiguous cases where a wrong merge does the most damage.
Related reading: HubSpot Merge Records Explained: What Happens to Child Objects, IDs, and Your Integrations