Insights 12 min read

Your attribution is broken and it is almost certainly the phone numbers

Before you blame the matching algorithm, look at how phone numbers are stored on each side. Extensions, country codes, leading zeros and a spreadsheet that decided a number was maths — this is where most attribution quietly fails.

A telephone headset resting on a desk beside a keyboard.
Photo by Pavel Danilyuk on Pexels

There is a specific conversation that happens about six weeks into every attribution project. The match rate is disappointing, everybody assumes the matching is not clever enough, and somebody proposes adding fuzzy name matching. It is almost always the wrong move, and the right move is unglamorous: go and look at how the phone numbers are stored.

Most attribution failures are not modelling failures. They are two systems writing the same phone number differently and nobody comparing them side by side.

This is the least interesting problem in marketing measurement and the one with the highest return on attention. Fixing identifier formatting routinely moves a match rate by ten to twenty points. No algorithm change does that.

Why phone numbers break more often than email

Email addresses have one canonical written form, so two systems recording the same address usually produce the same string. Phone numbers have many valid written forms of the same number, and every system picks a different one, so identical numbers routinely compare as different.

A telephone switch delivers a caller ID in full international form because it has to route across networks. A person typing into a CRM writes the number the way they would read it aloud, in the local convention, often with the spacing they find comfortable. Both records are correct. Neither is comparable to the other without work.

Email gets a much easier ride. There is casing, and there are provider quirks like dots being ignored by some mailbox providers, and that is broadly the extent of it. Lowercase both sides and you have solved most of it. Phone numbers have no equivalent one-line fix.

The eight faults that account for nearly all of it

Nearly every failed phone match comes from one of eight causes: country code present on one side only, punctuation and spacing, a national trunk zero, an extension, a mangled spreadsheet, more than one number per record, a non-geographic or virtual number, or simple transcription error.

The eight, what they look like, and what to do
FaultSales sideCall sideFix
Country code on one side only0207 123 4567+442071234567Normalise both to E.164 with a known default region
Punctuation and spacing(212) 555-0147+12125550147Strip everything that is not a digit or a leading plus
National trunk zero020 7123 4567+44 20 7123 4567Drop the trunk prefix when adding the country code
Extension+441614960000 ext 214+441614960000Split the extension off; match on the main number
Spreadsheet damage2.07123E+11+442071234567Re-export as text; do not attempt repair
Multiple numbers in one field07700 900123 / 0161 496 0000+447700900123Split on separators and try each
Virtual or tracking number+448081570000+441614960000Map the tracking pool to its destination before matching
Transcription error+447700900132+447700900123Accept the loss; do not fuzzy-match phone numbers

That last row deserves emphasis, because it is the one people want to argue with. Fuzzy matching phone numbers is a bad idea. Two numbers differing by one digit are not a near-match, they are two different people, and one of them is going to be a stranger who now appears in a report about somebody else's purchase. Names tolerate fuzziness. Identifiers do not.

Fuzzy-match names if you must. Never fuzzy-match a phone number. A digit out is a different human being, not a typo you get to forgive.

Normalise to E.164, and keep what you were given

The working rule is to store two values: the raw input exactly as the source system supplied it, and a normalised E.164 form used only for comparison. Matching runs on the normalised value; anything a person reads shows the raw one.

Keeping both matters more than it sounds. When somebody reviews a match and asks why these two rows were joined, the answer has to be inspectable. If the only stored value is the normalised one, the reviewer is looking at the output of the process they are trying to check, which is not a check.

  1. Determine a default region per dataset, not globally. A business operating in three countries needs three, because a nine-digit number with no country code means different things in each.
  2. Parse with a real library. libphonenumber knows that Italian numbers keep their leading zero internationally while British ones drop it. A regular expression does not.
  3. Split extensions into their own field before parsing, because they will otherwise defeat the parser or corrupt the number.
  4. Store the E.164 result and the raw original together, and never overwrite the original.
  5. Log what failed to parse. The failures are a data quality report you are getting for free.

The spreadsheet problem, which is worse than it looks

Opening a CSV of phone numbers in a spreadsheet application can destroy the data irreversibly. Leading zeros are dropped, long numbers are converted to scientific notation, and the file is saved back in its damaged state with no warning.

This is not a hypothetical. It is the single most common way a clean export becomes an unusable one, and it usually happens with the best intentions — somebody opens the file to check it looks right before sending it on.

The damage cannot be undone from the damaged file. A number that has become 2.07123E+11 has lost digits that are not recoverable by reformatting the cell. The only remedy is to export again and not open it, or to open it through an import dialogue that lets the column be typed as text before parsing.

The practical lesson is procedural rather than technical. Treat the export as evidence. It goes from the source system to the tool that reads it without being opened in between, and if somebody needs to inspect it, they take a copy.

Tracking numbers, and why they need mapping first

Dynamic number insertion replaces the displayed phone number with a tracking number drawn from a pool. That is useful for knowing which session generated a call, and it actively breaks matching, because the number in your call log is one your CRM has never seen.

The fix is to resolve tracking numbers to their destination before matching, using the mapping the call tracking provider already holds. Skipping this produces a distinctive and confusing symptom: the calls that were best instrumented are the ones that fail to match, so the channels you invested most in measuring look like the worst performers.

It is worth checking for this specifically whenever a paid channel appears to be underperforming by an implausible margin against a channel with no instrumentation at all. That pattern is more often a plumbing fault than a media one.

How to test whether your numbers are actually comparable

Take twenty sales you already know the source of, find their phone numbers in both systems, and compare the two strings character by character. If they differ, normalisation is your constraint, and no amount of algorithm work will substitute for fixing it.

Twenty is enough. This test takes about fifteen minutes, requires no tooling, and settles an argument that otherwise runs for a month. The reason to do it by hand is that a script comparing the two columns will apply some normalisation of its own and quietly hide the very difference you are trying to see.

  • Pick sales across the whole value range, not just the easy small ones.
  • Include at least three that came in by phone, because those are where the formatting divergence lives.
  • Write both strings down side by side. Do not eyeball them in two windows.
  • Count how many differ only in formatting. That count, as a share of twenty, is roughly the match rate you are leaving on the table.

CloseRev normalises both sides to E.164 with libphonenumber before any comparison, per dataset rather than globally, and shows the raw value beside the normalised one on every match so a reviewer can see exactly what was joined and why.

Email is easier, not safe

Email addresses match far more reliably than phone numbers, but they fail in their own characteristic way: the same person appears as a personal address on one side and a work address on the other, and no amount of normalisation will join those two strings.

This is the dominant email failure in business-to-business data and a significant one in high-value consumer purchases. Somebody researches on their personal address in the evening, then buys through a work address because that is where the invoice needs to go. Two real addresses, one real person, no possible textual match.

There are partial mitigations. Matching on the domain can link a work address to a company record. A shared phone number can bridge the two identities where both were captured. Neither is reliable enough to run unsupervised, which is why matches made this way belong in a review queue rather than in the automatically counted total.

The normalisation that is worth doing on email is modest and should be done anyway: trim whitespace, lowercase the whole address, and be careful about provider-specific rules such as plus-addressing and ignored dots. Treat anything beyond that as a judgement call rather than a transformation.

Multi-country data, where defaults become dangerous

A default country code applied globally will silently mis-parse every number from every other country. Country context belongs to the dataset, not to the system, because one business can be running three numbering plans at once.

The failure mode here is quiet and expensive. A nine-digit number with no country code is a valid number in several plans. Apply the wrong default and you do not get an error; you get a valid E.164 string for a completely different subscriber, which then either fails to match anything or, much worse, matches the wrong person.

Practically, this means the country is a property of the import — chosen when the file is mapped, alongside currency and timezone — and not a setting configured once when the account was created. Businesses expand into new markets far more often than they revisit their global configuration.

If you have one afternoon, do these four things

The highest-return sequence is: re-export both files as text without opening them, normalise both sides to E.164 with a per-dataset region, split extensions into their own column, and resolve any tracking numbers to their destinations. Everything else can wait.

That list is deliberately short and deliberately ordered. Each step is independent, each takes under an hour on most datasets, and each will usually produce a visible movement in the match rate on its own. The ordering matters because the first step protects the input for every step after it — normalising a file that has already been through a spreadsheet just produces a very tidy version of corrupted data.

What is not on the list is as important as what is. Do not tune matching thresholds, do not add name matching, and do not build a manual review queue yet. All three are reasonable things to do eventually, and all three will be doing work that formatting should have done, at much higher cost and with much worse accuracy.

The part nobody wants to hear

Identifier hygiene is a permanent operational discipline, not a one-off cleanup. Formats drift whenever a system is reconfigured, an integration is added, or a new team starts entering data, and the drift is silent until a match rate falls.

The teams that keep good attribution running are not the ones with the cleverest models. They are the ones who noticed that a phone number is a piece of infrastructure, gave it a defined format, and checked occasionally that the format was still being kept. That is a dull sentence and it is the most useful one in this article.

Every hour spent on identifier formatting returns more measurement accuracy than the same hour spent on any attribution model. It is also the hour nobody wants, which is exactly why the return stays high.

Questions people actually ask

What is E.164 phone number format?
E.164 is the international telephone numbering standard: a plus sign, a country code, and the national number, with no spaces, dashes or brackets, up to fifteen digits. A London number becomes +442071234567 and a New York number becomes +12125550147. It is the only format in which two phone numbers from different systems can be compared reliably.
Why do phone numbers fail to match between a CRM and a call log?
Because the two systems almost never store them the same way. One keeps the number as the caller ID delivered it, in full international form; the other keeps what a person typed, in national form with spaces. Compared as text they are different strings, so a correct match is missed and the revenue lands in the unattributed bucket.
Should I strip extensions from phone numbers before matching?
Yes, but keep the original. An extension identifies a desk inside a business, not the person who called you, so it will never appear on the caller ID side. Normalise to the main number for matching and retain the raw value so a human reviewing the match can still see what was recorded.
How do I fix phone numbers that Excel has mangled?
Re-export with the column formatted as text before it is opened, rather than trying to repair the file. Excel drops leading zeros, converts long numbers to scientific notation, and does both silently on open, so a file that has been opened and saved has already lost information that cannot be recovered from itself.
Do I need a library to normalise phone numbers?
For anything beyond a single country, yes. Numbering plans have irregular lengths, trunk prefixes that are dropped internationally, and country codes that overlap with area codes. libphonenumber, originally from Google, encodes those rules and is the practical default. Hand-written regular expressions handle the easy 80% and fail on exactly the numbers you care about.
Does normalising phone numbers create a privacy problem?
Normalisation itself does not, but the resulting file is still personal data. A phone number identifies a person, so it should be minimised, encrypted at rest, isolated per customer and deleted on a schedule, exactly as any other identifier would be.

See it on your own numbers.

Two exports and a few minutes. Three days free, no card, nothing to install.