
Why enhanced conversions show "on" but don't help
When Google Ads enhanced conversions aren't working, the tag is almost always firing fine but sending no usable user data at the moment the conversion fires. The setting can say active while every hit leaves with an empty or wrong hash. Google doesn't throw an error for that. You just get the same match rate you had before you turned it on.
We wanted to see exactly what the Google tag sends in the situations we keep running into on lead-gen sites, so we tested it. On October 28, 2026 we loaded the current gtag.js for a public Google Ads tag from an account with enhanced conversions turned on, put it on a test page, fired conversions in headless Chrome and captured every request before it left the machine. Then we read the em parameter, which is where the hashed email and phone travel.
Six things broke it. Most of them look completely healthy in Tag Assistant unless you know what to read.
What changed in 2026 (and what didn't)
Two things moved this year, and both are on the Google Ads side, not in the browser:
- One toggle. In June 2026 Google merged enhanced conversions for web and for leads into a single on/off setting, and since April 2026 Google Ads accepts user-provided data from the tag, Data Manager and API connections at the same time. Existing accounts were migrated automatically (Google's announcement).
- Lead uploads moved. Offline conversion and enhanced-conversions-for-leads uploads were blocked in the Google Ads API on June 15, 2026 and moved to the Data Manager API. We covered that migration in our Data Manager API guide.
What didn't change is the tag. gtag('set', 'user_data', {...}) still has to put the right values on the page before the conversion event fires, on the same page, over HTTPS, with consent. The single toggle actually makes this easier to miss, because there's no longer a method screen reminding you that the tag needs code.
How to read the em parameter
Open Chrome DevTools, filter the Network tab for conversion/, submit a test lead and click the googleadservices.com/pagead/conversion/... request. The em query parameter tells you what happened:
tv.1~em.<hash>~pn.<hash>: a hashed email and phone were sent. Good, but still check the phone (see below).tv.1with nothing after it: the enhanced conversions code is on, but there was no user data on the page when the conversion fired.tv.1~ec.e3: the page isn't HTTPS. Nothing was hashed.- No
emparameter at all: the account hasn't accepted the customer data terms, orad_user_dataconsent is denied.
There's also an ec_mode parameter. In our hits, c meant the data came from your code and a meant the tag picked it up automatically from the page. That one matters more than it sounds.
The six ways it broke in our tests
1. The page was served over HTTP
On plain HTTP, every conversion went out with em=tv.1~ec.e3. The shipped gtag.js source checks location.protocol and returns error code e3 before it touches the data. This is the classic staging-site trap: someone tests on an HTTP preview, sees the conversion fire, and signs off. On HTTPS the exact same page sent a full hash.
2. Consent denied ad_user_data
With ad_user_data set to denied in Consent Mode, the conversion hit still fired, but there was no em parameter at all, and the gcd consent string showed the denial. That's by design. If your banner defaults everything to denied and the update never lands, enhanced conversions are off for every visitor no matter what the setting says. Our consent mode audit walks through checking that update.
3. user_data was set after the conversion event
Order matters. When we called the conversion event first and gtag('set', 'user_data', ...) a moment later, the tag ignored our code data. Because automatic detection was on in that account, it sent an email it found in a form field on the page instead, with ec_mode=a.
That's worse than sending nothing. On a page with a newsletter box in the footer or a pre-filled field, the tag can hash an address that isn't the lead's. Set user_data first, then fire the conversion, or pass user_data inside the event itself. Both worked in our test.
4. The form and the conversion were on different pages
This is the most common lead-gen setup: the form posts, the browser lands on /thank-you, and the conversion fires there. We set user_data on the form page, navigated to a thank-you page without a form, and fired the conversion. The hit went out with em=tv.1, empty. set doesn't survive a page load.
Fix it one of three ways: fire the conversion on the form page when the submit succeeds, carry the values to the thank-you page (session storage or the confirmation response) and set them there, or send the lead server-side through Data Manager instead.
5. Phone numbers without the country code
Google's docs say phone numbers must be E.164, like +12145550100. What we didn't expect is what the tag does when they aren't. We passed (214) 555-0100 and 2145550100. The tag stripped the punctuation, added a +, checked the length and hashed +2145550100 in both cases. We confirmed it by hashing the candidates ourselves.
No error, a valid-looking hash, and the wrong phone number. +2145550100 isn't a Dallas number, so it'll never match the person's Google account. US forms almost never collect the +1, so most US sites sending phone through the tag are sending this. Add the country code before you call set:
const digits = phone.replace(/\D/g, "");
const e164 = digits.length === 10 ? "+1" + digits : "+" + digits;
gtag("set", "user_data", { email, phone_number: e164 });
gtag("event", "conversion", { send_to: "AW-XXXXXXXXX/your-label" });
That snippet assumes US traffic. If you take international leads, use a phone library on the form rather than guessing the country.
6. The account never accepted the data terms
For comparison we loaded the tag for a made-up Ads ID. Its container had no first-party data tag in it at all, and the conversion hits carried no em parameter whatever we set. Google's setup page says the tag checks this in real time and won't collect user-provided data for a destination that hasn't accepted the customer data terms. If you inherited an account, check Goals, then Settings, then Customer data use, before you debug a single line of code.
What the tag gets right on its own
Not everything needs fixing in your code. A few things we confirmed so you don't waste time on them:
- Email normalization is done for you. We passed
" John.Doe@Gmail.com "and the tag hashedjohndoe@gmail.com: trimmed, lowercased, Gmail dots removed. You don't need to normalize emails yourself if you send raw values. - Pre-hashed values pass through. A hex SHA-256 in
sha256_email_address, or even in the plainemailfield, went out unchanged. Just make sure you normalized before hashing, because the tag can't fix a hash. - Name and address work too. First and last name were hashed. Postal code and country were sent in plain text, which matches Google's spec, since those two aren't hashed.
One thing the tag won't stop you from doing: sending a phone number alone. It sent pn with no email. Google's docs say phone has to come with an email or a full name and address, so don't rely on phone-only matching.
A five-minute check for any lead form
- Use the live HTTPS site, not staging, with DevTools open and the Network filter on
conversion/. - Accept consent like a real visitor.
- Submit a test lead with a phone typed the way your visitors type it.
- Read
emon the conversion request and compare it to the list above. - Hash your test values yourself (SHA-256, base64url, which is what the tag sends) and confirm the
pnvalue matches+1plus the number.
If any of that fails, the enhanced conversions diagnostics report in Google Ads will catch up eventually, but it can lag a day or two. The network tab tells you in seconds. And if your leads close offline, the tag is only half the job: Smart Bidding also needs the lead quality signal coming back, which is the part we cover in filtering invalid leads out of Smart Bidding. If iOS link tracking protection is also eating your click IDs, matched user data is the backup that still works, as we explain in our iOS 26 click ID guide.
How we tested this
On October 28, 2026 we served a local test page over HTTP and over HTTPS (self-signed certificate) and ran it in headless Chromium 148 with Playwright 1.60. The page loaded the current gtag.js for a public Google Ads tag ID taken from a live advertiser site with enhanced conversions and automatic detection enabled, and, for comparison, a made-up Ads ID. Every request to Google domains was intercepted and answered locally, so no test data reached Google. We fired 15 scenarios with test emails and a 555 phone number, decoded em from each conversion request, and verified every hash by computing SHA-256 ourselves. The HTTPS check and the phone normalization rules come from the shipped tag source. The user_data fields and E.164 requirement are documented in Google's enhanced conversions setup guide.
Get a Free Automation Audit
If you're spending on Google Ads and can't say for sure what your tag sends when a lead converts, get a Free Automation Audit. We'll check your enhanced conversions, consent setup and offline conversion flow end to end and show you what Smart Bidding is actually learning from. Or see how we run ads management with tracking we can prove.
