How to Hash PII for Enhanced Conversions: Normalization and SHA-256 Guide
A detailed guide to normalizing and hashing PII (email, phone, name) for Enhanced Conversions across Google Ads, Meta, Microsoft Ads, and TikTok. The Upload Offline Conversions App explains the normalize-then-hash pipeline.
Enhanced Conversions require all personally identifiable information (PII) to be SHA-256 hashed before sending to ad platforms. But hashing alone isn’t enough — you must normalize the data first. This guide covers the exact normalization and hashing pipeline for every supported field.
The Golden Rule: Normalize → Hash
RAW DATA → NORMALIZE → SHA-256 HASH → SEND TO PLATFORM
Never skip normalization. The hash of John@Email.COM is completely different from john@email.com, but they’re the same email address. If you hash without normalizing, your data won’t match the platform’s records.
Email Normalization and Hashing
Step-by-Step
Raw: " John.Doe@Gmail.COM "
Step 1: "John.Doe@Gmail.COM" ← Trim whitespace
Step 2: "john.doe@gmail.com" ← Lowercase
Step 3: SHA-256("john.doe@gmail.com") ← Hash
Result: "e8b6c3a4f2d1b5e7..." ← 64-char hex string
Platform-Specific Email Rules
| Rule | Google Ads | Meta | Microsoft | TikTok |
|---|---|---|---|---|
| Trim whitespace | ✅ | ✅ | ✅ | ✅ |
| Lowercase | ✅ | ✅ | ✅ | ✅ |
| Remove Gmail dots | ✅ | ❌ | ✅ | ❌ |
Remove + aliases | ❌ | ❌ | ❌ | ❌ |
Gmail dot removal: Google treats john.doe@gmail.com and johndoe@gmail.com as the same address. Before hashing for Google Ads, remove dots from the local part of @gmail.com addresses:
"john.doe@gmail.com" → "johndoe@gmail.com" → hash
"john.doe@yahoo.com" → "john.doe@yahoo.com" → hash (NOT Gmail, keep dots)
Meta does NOT do this — hash the email as-is (with dots).
Code Example
function normalizeEmail(email, platform = 'google') {
let normalized = email.toLowerCase().trim();
// Google-specific: remove dots from Gmail local part
if (platform === 'google' || platform === 'microsoft') {
if (normalized.endsWith('@gmail.com')) {
const [local, domain] = normalized.split('@');
normalized = local.replace(/\./g, '') + '@' + domain;
}
}
return normalized;
}
Phone Normalization and Hashing
Step-by-Step
Raw: "(555) 123-4567"
Step 1: "5551234567" ← Remove non-digits
Step 2: "+15551234567" ← Add country code (E.164)
Step 3: SHA-256("+15551234567") ← Hash
Result: "d4f5c3b1e72a..." ← 64-char hex string
E.164 Format Rules
| Format | Example | Valid? |
|---|---|---|
+15551234567 | US number | ✅ |
+445551234567 | UK number | ✅ |
5551234567 | Missing country code | ❌ |
(555) 123-4567 | Not stripped | ❌ |
1-555-123-4567 | Missing + | ❌ |
The + is included in the hash for all platforms.
Use the E.164 Formatter tool to verify your phone formatting.
Code Example
function normalizePhone(phone, countryCode = '1') {
// Remove all non-digit characters
let digits = phone.replace(/\D/g, '');
// Handle US numbers
if (countryCode === '1') {
if (digits.length === 10) {
digits = '1' + digits;
}
if (digits.length === 11 && digits.startsWith('1')) {
return '+' + digits;
}
}
return '+' + digits;
}
Name Normalization and Hashing
Step-by-Step
Raw: " John "
Step 1: "John" ← Trim whitespace
Step 2: "john" ← Lowercase
Step 3: SHA-256("john") ← Hash
Result: "a8cf5e2d..." ← 64-char hex string
Rules
- Trim leading/trailing whitespace
- Lowercase
- Do NOT remove middle names, suffixes (Jr., III), or accented characters
- Hash first name and last name separately
Address Normalization
| Field | Normalization | Example |
|---|---|---|
| City | Lowercase, trim | " New York " → "new york" |
| State | 2-letter abbreviation, lowercase | "California" → "ca" |
| Zip | First 5 digits only (US) | "90210-1234" → "90210" |
| Country | ISO 2-letter, lowercase | "United States" → "us" |
Address fields are lower priority for matching but can improve match rates when combined with email and phone.
Common Mistakes
| Mistake | What Goes Wrong | Fix |
|---|---|---|
| Hash before normalize | Zero matches | Always normalize first |
| Use MD5 | Zero matches | Use SHA-256 only |
| Include whitespace | Lower match rates | Trim before hashing |
| Wrong phone format | Zero phone matches | Use E.164 (+1XXXXXXXXXX) |
| Hash twice | Zero matches | Hash exactly once |
| Uppercase hex output | May cause issues | Use lowercase hex |
| Don’t remove Gmail dots (Google) | Lower match rates | Remove dots for @gmail.com |
Verifying Your Hashes
Manual Verification
Use the SHA-256 Hasher tool to verify:
- Enter a normalized email:
john@example.com - Click Hash
- Compare the output with your implementation’s output
- They must match exactly
Programmatic Verification
// Known test vector
const input = "john@example.com";
const expected = "fd876f5e45d2e29fd23fd8a4c8579323fb18c2d1e79c2e749fc603e3c03a1c78";
const actual = await sha256(input);
console.assert(actual === expected, "Hash mismatch!");
How TUOCA Handles Hashing
TUOCA automates the entire pipeline:
- Raw PII input — You provide unhashed data from your CRM/call tracking
- Platform detection — TUOCA identifies the destination platform
- Platform-specific normalization — Gmail dot removal for Google, standard for Meta, etc.
- SHA-256 hashing — All PII fields hashed after normalization
- Format validation — Verifies 64-character lowercase hex output
- Upload — Sends hashed PII to the appropriate API
You never need to pre-hash your data. TUOCA handles everything automatically.