Free tool · no sign-up
SHA-256 tester for enhanced conversions
Check whether Google Ads and Meta receive your customer’s hashed email and phone number, or a hash of an empty field or undefined that no conversion will ever match. Without BigQuery you check a hash from GTM Preview. With BigQuery you check every stored hash in one query.
- 107 empty and test values for email and phone
- Normalisation rules from the Google Ads, GA4 and Meta docs
- Online version and a BigQuery SQL query
- Your data never leaves your browser
Where are the hashes you want to check?
Your browser does not provide the SHA-256 function (Web Crypto). Open this page over https:// in an up-to-date Chrome, Firefox, Edge or Safari.
- Submit a test form with GTM Preview switched on.
- Copy the hash from the user data variable and paste it below.
- Also enter what you typed in the form. The tester tells you whether the hash matches.
sha256_email_address: "…". The tester extracts the hash itself.You have used your 3 free checks for today
Enter your email and I will unlock the tester with no limit on this device straight away. No need to wait for a message.
Hashing happens in your browser. The data and hashes you enter are not sent to a server or stored.
- Find the table where you store the hashes sent to Google Ads or Meta.
- Fill in the fields. The tester builds the SQL query instantly.
- Run it in the BigQuery console and see what percentage of hashes is broken.
You run the query in your own BigQuery project. The tester never connects to your data.
Did the tester find a problem? I will show you where the tag gets the wrong data and how to fix it.
Book a 30-min consultationReference list
SHA-256 hashes of empty and invalid values
If the hash from your website is on this list, your tag sends Google Ads or Meta an empty or random value instead of customer data. The fastest check is Ctrl+F (Cmd+F on a Mac): paste the start of your hash.
Email field
| Hashed value | SHA-256 | Where it comes from |
|---|---|---|
| (empty string) | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 | empty variable: the tag fired before the form stored the email |
| (space) | 36a9e7f1c95b82ffb99743e0c5c4ce95d83c9a430aac59f84ef3cbfab6145068 | a space in an empty form field |
null | 74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b | the variable is null |
NULL | fb329000228cc5a24c264c57139de8bf854fc86fc18bf1c04ab61a2b5cb4b921 | NULL from a database or CRM, not converted to lowercase |
undefined | eb045d78d273107348b0300c01d29b7552d622abbc6faf81b3ec55359aa9950c | the variable does not exist and the code turned it into text |
none | 140bedbf9c3f6d56a9846d2ba7088798683f4da0c248231336e6a05679e4fdfe | None from server-side code, after normalisation |
nan | 9b2d5b4678781e53038e91ea5324530a03f27dc1d0e5f6c9bc9d493a23be9de0 | JavaScript NaN after normalisation |
0 | 5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9 | the number zero instead of an empty field |
false | fcbcf165908dd18a9e49f7ff27810176db8e9f63b4352213741664245224f8aa | a boolean instead of data |
[object object] | c9a560c74de368dcd2e6ac5544ad12a9afd32584b71a52147303479f726e0408 | a JavaScript object turned into text, after normalisation |
{{email}} | 2fa568238f6d6aa0d84b7d66ec13b95eef51f803f1fdc5b8c54e475574ee6de3 | an unfilled variable in a template or tag |
email | 82244417f956ac7c599f191593f7e441a4fafa20a4158fd52e154f1dc4c8ed92 | the field name instead of its value |
e-mail | 39e0749da40f625e3ac47efdf64b79fa28f8aad01f3c2746176ea4a03a9e98e1 | the field label instead of its value |
your email | 4971024aa5990614e2b59315e61154b9e6200060679060147a3f16db779eddaf | placeholder text from the form |
test@test.com | f660ab912ec121d1b1e928a0bb4bc61b15f5ad44d5efdc4e1c92a25e99b8e44a | a test address from the form or the code |
test@example.com | 973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b | a sample address from documentation or code |
| (empty string, hashed twice) | cd372fb85148700fa88095e3492d3f9f5beb43e555e5ff26d95f5a6adc36f8e6 | an empty variable hashed twice |
Phone field
| Hashed value | SHA-256 | Where it comes from |
|---|---|---|
| (empty string) | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 | empty variable: the tag fired before the form stored the phone number |
+44 | 23ead39c5af7f86bc6b2466a8ae10442c3663bdc4422723bc54def68caf184f6 | only the country code added to an empty field (Google Ads) |
44 | 71ee45a3c0db9a9865f7313dd3372cf60dca6479d46261f3542eb9346e4a04d6 | only the country code added to an empty field (Meta) |
+44undefined | 482c65fe3d97b214c3199c56dcf81abbe91155505348544f70d5fe11f9303fd1 | country code added to a variable that does not exist |
44undefined | 53e343855cdbab3a2354c32bd5ae7327c22439ba1f76f9db5d8e1177ec26adde | the same with Meta normalisation |
+44null | e5ce5543605c8701eaab31b25e671249db6740e4efdbba2fbbe92d3af93fbb52 | country code added to a null variable |
+1 | c59dc4e44ff99288156d4dff2168f6ac7ddee6b1fc7ccc0754656ffaa6d351ea | only the US/Canada country code added to an empty field |
0 | 5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9 | the number zero instead of a phone number |
123456789 | 15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225 | a test number |
+44123456789 | 3d84a3838599719df7deacc7fb91903bde5430a8c0e007c3eba93bce0c69c5a2 | a test number with the country code |
phone | 45569da57f4b7bf472d7a864ef4781451cae6383fee9fb0ae40c59aa1ce475b7 | the field name instead of its value |
The tester recognises more values than this list, including other country codes, uppercase spellings and values hashed twice.
How to use it
Two paths: without BigQuery and with BigQuery
Without BigQuery you check one test form in a few minutes. With BigQuery you see what percentage of all your conversions sends a broken hash.
I don’t have BigQuery
- Submit a test form. Turn on GTM Preview (and server-side GTM Preview, if you have it) and submit a form or an order with your own test email and phone number.
- Copy the hash. In GTM Preview, click the conversion event and open the user data variable. In server-side GTM check the
user_datafield. For Meta, use the Test Events tab in Events Manager. - Choose email or phone, and the platform. Phone normalisation differs between Google Ads and Meta, so the result depends on this choice.
- Paste the hash and the form data. The tester shows whether the hash matches and, if it doesn’t, whether it is an empty value or a normalisation error.
I have BigQuery
- Find the table with the hashes. Hashes are only in BigQuery where you store them yourself: in server-side GTM logs, an offline conversion upload table or CRM data.
- Fill in the tester fields. Choose email or phone, the platform, the table and the hash column. If the same table holds the plain data, add its column too.
- Run the SQL in the BigQuery console. The query uses the
SHA256andTO_HEXfunctions. Add a date filter so you don’t scan the whole history. - Read the result. You see the number and percentage of rows: no hash, empty value, wrong format, uppercase, hash not matching the data, and valid hashes.
Where empty hashes come from
6 mistakes that send Google Ads and Meta a hash instead of a customer
In Preview, the hash of an empty field looks just like a valid email: 64 characters of digits and letters. That is why this bug can survive for months, while enhanced conversions report a low match rate.
- The tag fires too early. A button click trigger fires the tag before the form writes the email to the dataLayer. An empty string gets hashed.
- Custom code hashes a missing value. The variable returns
undefined, and the JavaScript code turns it into the text “undefined” and hashes it. - A form in an iframe or a pop-up. GTM on the page cannot see the field in an embedded form, so the variable picks up the field label or nothing.
- Double hashing. A shop or CRM plugin already sends a hashed email, and the tag hashes it a second time.
- No normalisation. Uppercase letters, spaces, dots and a plus suffix in Gmail, or a phone number without the country code, produce a hash of different data than what Google or Meta know.
- Wrong hash encoding. Uppercase letters in the hash, or Base64 instead of hexadecimal (hex), when you hash the data yourself.
From my projects: on a diet catering website, Google Ads reported a critical enhanced conversions error because the data was hashed with SHA-1 instead of SHA-256. On another catering platform, the hashed phone number in E.164 format was missing from the dataLayer. See the case study
Cheat sheet
Normalising email and phone before SHA-256: Google Ads and Meta
Google Ads and Meta normalise data differently, especially phone numbers. Changing a single character changes the whole hash, so the data has to be written exactly as the platform you send it to requires.
| Step | Google Ads and GA4 | Meta Conversions API |
|---|---|---|
| Unhashed data | allowed: the Google tag normalises and hashes it before sending | not allowed: em and ph must be hashed (the browser pixel hashes on its own) |
| no leading or trailing spaces, lowercase; for gmail.com and googlemail.com remove dots before the @ and the plus suffix, in other domains remove nothing | no leading or trailing spaces, lowercase; do not remove dots | |
| Plus suffix | remove it in Gmail (name+test@gmail.com → name@gmail.com), keep it in other domains | the documentation does not remove it |
| Phone | E.164 format: plus, country code, no spaces or hyphens, e.g. +447700900123 | digits only with the country code, no symbols or leading zeros, e.g. 447700900123 |
| Hash | if you hash it yourself: SHA-256 in hex, 64 characters 0-9 and a-f, lowercase; a Google tag that receives raw data hashes it itself in web-safe Base64 | SHA-256 in hexadecimal (hex): 64 characters 0-9 and a-f, lowercase |
Email normalisation examples for Google Ads and GA4
| Email entered | After normalisation | Start of the hash |
|---|---|---|
| JOHNsmith@company.com | johnsmith@company.com | b339b4e8fe19… |
| j.o.HNSmith+test@company.com | j.o.hnsmith+test@company.com | 934050e71cf4… |
| john..smith@gmail.com | johnsmith@gmail.com | 3586de92bb36… |
| johns.m.ith@googlemail.com | johnsmith@googlemail.com | b1dd4aa5ffe1… |
| J.o.HNSmith+test@gmail.com | johnsmith@gmail.com | 3586de92bb36… |
An inconsistency in Google’s documentation that few people know about. The Google Ads help page on enhanced conversions with the Google tag and the GA4 documentation only tell you to remove dots from Gmail addresses. The Google Ads API documentation goes further: for gmail.com and googlemail.com you also have to remove the plus suffix, or the hashes will not match. The example from that documentation: Jane.Doe+Shopping@googlemail.com → janedoe@googlemail.com. I found this inconsistency by comparing Google’s documents, which is why the tester applies the full rule from the API documentation.
Dots and the plus suffix are only removed in Gmail addresses. In other domains, including company domains, the dot and the plus are part of the address, so keep them. Sources: Google Ads API, Email address formatting for enhanced conversions; GA4, Send user-provided data using Measurement Protocol; Google Ads, Set up enhanced conversions for web using the Google tag; Meta, Customer Information Parameters.
How it works
SHA-256: one-way encryption of customer data
SHA-256 is a hash function: it turns an email or phone number into a 64-character string from which the data cannot be recovered. That is why people call it one-way encryption. A regular cipher has a key and can be decrypted, while a hash has no key and only works in one direction.
Google and Meta require SHA-256 because it lets them compare customer data without receiving it in plain text. Conversion matching works like this:
Your website hashes the customer’s email
The address
john@gmail.comalways produces the same hash,142d78e466ca…. Only this hash reaches Google or Meta.The platform hashes its users’ addresses
Google and Meta compute SHA-256 from account addresses, using the same normalisation rules described in the cheat sheet above.
It compares hashes, not emails
When the hashes are identical, the conversion is attributed to the person who clicked the ad. The platform never gets a list of plain-text addresses from you.
This leads to the most important rule: changing a single character changes the whole hash. john@gmail.com gives 142d78e466ca…, while John@gmail.com gives 258d4f75ef1d…. Without identical normalisation on both sides, matching fails. For the same reason the hash of an empty field is always the same, so the tester recognises it by its value.
A hash is not anonymisation. It cannot be reversed, but anyone can hash a known address and compare the result. That is why, under the GDPR, a hashed email is still personal data and needs a legal basis and ad_user_data consent in Consent Mode.
Ready-made implementation
A correct email and phone hash for Google and Meta, without guessing the rules
Based on the rules in this tester, I built ready-made normalisation and hashing of user data for Google Ads, GA4 and Meta CAPI. It covers the Gmail rule from the Google Ads API documentation, the different phone formats for Google and Meta, and a block that stops the hash of an empty field from being sent. It comes in two versions.
Version 1 · better and more reliable
Developer guidelines for Jira
A ready Jira ticket: dataLayer specification, email and phone normalisation rules for Google and Meta, test data with expected hashes and acceptance criteria. A developer implements the hashing in the website’s code.
Why it’s better: the website passes the data at the moment the form or order is submitted, so it does not depend on how the form looks or on the tag order in GTM.
Version 2 · no developers needed
Ready GTM container (JSON file)
You import the file into Google Tag Manager and get variables that normalise and hash email and phone separately for Google and Meta. Implement it yourself or with me.
When to choose it: when developers have no time or are not available, and enhanced conversions need fixing now.
Combining both versions works best. Until the developer ships the change, make the GTM-side fix right away so you don’t lose conversions. Once the developer adds the hashes to the dataLayer, the container switches to them automatically, with no changes in GTM.
Code only
€25 net
Both versions in one package: the GTM container (JSON file) with setup instructions, and a Jira ticket with JavaScript and PHP code, test data and acceptance criteria.
Full implementation
from €125 net
I implement the container in your GTM, connect it to Google Ads, GA4 and Meta, then check the hashes in GTM Preview and in the tester. The price depends on how your website and forms are built.
Tell me whether you need the code only for €25 net, or the full implementation.
FAQ
Questions about hashing email and phone for enhanced conversions
Does Meta accept an unhashed email and phone number?
Not in the Conversions API. The em and ph fields must be SHA-256 hashed, and Meta rejects unhashed contact data. The Meta Pixel in the browser (advanced matching) hashes the data itself.
Should I send Google Ads the email in plain text or hashed?
Google Ads accepts both. The Google tag normalises and hashes unhashed data itself before sending it to Google. Keep in mind that a SHA-256 hash is pseudonymisation, not anonymisation: under the GDPR a hashed email is still personal data. In both cases you need a legal basis and ad_user_data consent in Consent Mode. Every script on the page can see a plain email in the dataLayer, so limit where it appears. Agree on the details with your lawyer or data protection officer.
Should I remove dots and the plus suffix from an email address?
Only in Gmail addresses. For Google Ads and GA4, in the gmail.com and googlemail.com domains remove dots before the @ sign and the plus suffix: j.ohn+test@gmail.com becomes john@gmail.com. In other domains remove nothing, because j.ohn@company.com and john@company.com can be two different mailboxes. The Google help page and the GA4 documentation only mention dots; removing the plus suffix is described only in the Google Ads API documentation. Meta removes neither dots nor the suffix, it only requires lowercase and no spaces.
Hex or Base64 hash?
If you hash the data yourself, before passing it to the tag or to an upload, use SHA-256 in hex: 64 characters 0-9 and a-f. That is what Google Ads, GA4 and Meta require. Base64 appears when you pass raw data to the Google tag: the tag hashes it itself and encodes it in web-safe Base64. The tester recognises both and compares them with the form data.
Can you read an email from a SHA-256 hash?
Not directly. SHA-256 works one way, so the address cannot be mathematically recovered from the hash. However, anyone can hash a known address and compare the result, which is why a hashed email is still personal data under the GDPR.
How does phone normalisation differ between Google Ads and Meta?
Google Ads requires E.164 format with a plus, e.g. +447700900123. Meta requires digits only with the country code, without a plus or leading zeros, e.g. 447700900123. The same data therefore gives two different hashes.
Why is my hash e3b0c442…b855?
That is the SHA-256 of an empty string. The tag hashed an empty variable because it fired before the form wrote the data to the dataLayer, or the GTM variable points to the wrong field. Google Ads and Meta will not match such a conversion to any user.
Can I use an email hash as the user_id in GA4?
No. Google requires that the user ID does not contain information a third party could use to identify the user. A SHA-256 hash of an email without an extra key is easy to match to a known address. Use the internal customer ID from your own system as the user_id.
Where do I find the hash my website sends?
In GTM Preview, select the conversion event and check the user data variable. In server-side GTM, open the event in Preview and check the user_data field or the outgoing request to Meta. In BigQuery you will only find hashes in tables you write them to yourself, such as server-side GTM logs or an offline conversion upload.
Does a green result mean everything works?
Not quite. It means the hash has a valid format and is not one of the known empty values. To be sure it is the right data, enter what you typed in your test form into the tester.
Does the tester have a check limit?
Without entering your email you can check a hash 3 times a day on one device. Once you enter your email, the tester has no limit. The BigQuery path has no limit, because you run the SQL query yourself.
Does the tester send the data I enter to a server?
No. Hashing and comparison happen in your browser, and the site statistics only receive the number of hashes checked, never their content. The only thing that reaches the server is your email address, if you choose to enter it to remove the limit. For testing, use your own test email address and phone number.
The tester found an empty hash? I will help you fix it
In a 30-minute consultation we check together where the tag gets its data, and agree what to fix in the dataLayer, GTM or server-side GTM so Google Ads and Meta match conversions to customers.