Skip to content

Identifier normalization & hashing

Hashing is exact. Two values that differ by a single space, a capital letter or a leading zero produce completely unrelated hashes, and the platform has no way to tell they were the same person. Normalization is the step that makes both sides agree before the hash is taken.

This page matters more than any other in this section. Most poor match rates trace back to something here.

Signals hashes personal identifiers with SHA-256. Hashing is one way: the output cannot be reversed to recover the original value. It is not encryption, and it should not be described as encryption.

Output is a lowercase hexadecimal string, 64 characters long.

The right default is to hash personal data before it leaves your systems. It means raw identifiers never travel, and it puts normalization in the place that best understands the data.

On website and app sources this happens for you. The tag hashes in the browser and the app hashes before posting, so the raw value never leaves the device.

On CRM, file, database and warehouse sources, send values already hashed if your systems can do it. Where they cannot, Signals normalizes and hashes on the way through, in memory. The raw value is not written to disk and nothing downstream sees it.

These are the rules Signals applies. Apply the same ones if you are hashing before you send.

FieldRule
EmailTrim leading and trailing spaces. Convert to lowercase.
PhoneE.164. A leading plus sign, then the country code and the number, up to fifteen digits, with whitespace, brackets, hyphens and any leading zero removed.
First name, last nameLetters only. Trim whitespace, lowercase, no punctuation or special characters, UTF-8.
CityLetters only. Trim whitespace, lowercase, no punctuation or special characters, UTF-8.
StateIn the US, the two character ANSI abbreviation in lowercase. Outside the US, lowercase with no punctuation, special characters or whitespace.
CountryLowercase two letter ISO 3166-1 alpha-2 code.
PostcodeLowercase, no whitespace. In the US, the first five digits only. In the UK, Area/District/Sector format.
Genderm for male, f for female.
Date of birthDD/MM/YYYY. It can also be split into year YYYY, month 01 to 12, and day 01 to 31.
InputNormalizedHashed
Person@Example.comperson@example.com64 character lowercase hex
+971 56 789 1234+97156789123464 character lowercase hex
Mumbaimumbai64 character lowercase hex
Indiain64 character lowercase hex
2 July 199902/07/199964 character lowercase hex

This is the single most common cause of a low phone match rate, and it is worth stating on its own.

Meta requires the country code on every phone number, and states that you should “always include the country code as part of your customers’ phone numbers, even if all of your data is from the same country”. A national format number will not match.

The failure is invisible. A UAE number stored as 05x xxx xxxx or an Indian number stored as 0 98xxx xxxxx hashes perfectly well. It just hashes to a value no platform has ever seen. Strip the leading zero, prefix 971 or 91, then hash.

Three categories of value must be sent unhashed. Hashing them breaks them.

  • Technical identifiers. Click IDs and platform cookies such as fbc and fbp, plus client_ip_address and client_user_agent.
  • Device advertising identifiers and external IDs. GAID, IDFA, IDFV and your own external ID are sent as they are.
  • Meta’s per-channel messaging identifiers. ctwa_clid for Click to WhatsApp, ig_sid and ig_account_id for Click to Instagram, and page_id and page_scoped_user_id for Click to Messenger. Meta flags all five do-not-hash. They are the strongest match key on Messaging CAPI, so a hash here costs more than it does on a contact field.

Meta states that its systems are designed not to accept unhashed contact information, so the split is enforced on the platform side as well.

City, state, country and postcode are hashed for Meta and sent in plain text for Google. Email address and phone number are still hashed for every destination including Google, so this applies to the location fields only.

On a file source the template carries both versions of each location field for that reason. Use the _hashed columns for Meta and the plain city, state, country and zipcode columns for Google. Hashing the plain columns is a common cause of a Google match rate that is lower than the Meta one on the same file.

Normalize before you hash, because nobody can check afterwards

Section titled “Normalize before you hash, because nobody can check afterwards”

A SHA-256 hash of an un-normalized value looks exactly like a hash of a normalized one. Both are 64 character hex strings, and there is no way to tell them apart.

That means Signals cannot detect that you skipped normalization, and neither can the destination. Nothing errors. The hash simply never matches anything, and your match rate degrades silently. If you hash at source, the normalization rules above are not optional advice, they are the difference between a value that matches and one that never will.

The offline conversions interface accepts up to three email addresses and up to three phone numbers per event, with two format variants available per phone number. Send E.164 in the format_b column. Where you hold more than one contact detail for a person, send them all. Each is an additional matching opportunity.

Datahash normalizes and hashes to the rules above. Individual platforms publish their own requirements, and where they differ the platform’s rule governs what it will accept.

  • Meta requires em, ph, fn, ln, db, ge, ct, st, zp and country to be hashed, and publishes normalization and SHA-256 test vectors per parameter.
  • Snapchat normalizes then SHA-256 hashes personal identifiers, and takes client_ip_address and client_user_agent unhashed on web events.
  • TikTok requires email and phone hashed with SHA-256 before they reach TikTok, browser side for pixel Advanced Matching and server side for the Events API.