Tutorial: Implementing Google Ads Enhanced Conversions

Contents
Google Ads Enhanced Conversions significantly improve measurement accuracy by recovering conversions that would otherwise be lost due to cookie restrictions or cross-device journeys. By securely transmitting hashed first-party customer data (such as an email address) to Google, the system can match this data against signed-in Google accounts, attributing the conversion to the correct ad click.
The following tutorial explains the process of capturing an email address after a purchase or lead generation, ensuring it is hashed properly, and sending it via Google Tag Manager (GTM).

Step 1: Enabling the Feature in Google Ads
Before any technical implementation begins, Enhanced Conversions must be enabled within the Google Ads account. This is done by navigating to the Conversions section, selecting the specific conversion action (e.g., Purchase or Lead), and checking the box to turn on Enhanced Conversions. The “Google Tag or Google Tag Manager” method should be selected.
Step 2: Capturing the Email Address
The core of the implementation is extracting the customer’s email address on the “Thank You” page. There are two primary methods to achieve this:
Method A: DataLayer (Recommended)
The most robust and reliable approach is pushing the email address directly into the DataLayer from the server or CMS. A standard DataLayer push upon a successful transaction looks like this:
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'purchase',
'customerData': {
'email': 'customer@example.com'
}
});
</script>
In GTM, a Data Layer Variable must be created pointing to the key customerData.email.
Method B: CSS Selectors (Fallback)
If modifying the DataLayer is not possible, the email can be extracted directly from the website’s Document Object Model (DOM). If the email is displayed on the confirmation page (e.g., <span id="customer-email">customer@example.com</span>), a DOM Element Variable can be created in GTM. The selection method should be set to “CSS Selector” with the selector targeting the specific element (e.g., #customer-email).
Step 3: Creating the User-Provided Data Variable
Once the email is captured as a GTM variable, it must be formatted correctly for Google Ads.
- A new variable of the type User-Provided Data is created in GTM.
- The “Manual configuration” option is selected.
- The previously created email variable (from DataLayer or DOM) is assigned to the “Email” field.
Step 4: Hashing and Transmission
Privacy regulations require that personal data is never sent in plain text. A major advantage of using the GTM “User-Provided Data” variable is that it automatically applies the required SHA-256 hashing algorithm to the email address before transmission. No manual JavaScript hashing is required.
Step 5: Attaching to the Conversion Tag
The final step involves linking the configured data to the tracking tag.
- The existing Google Ads Conversion Tracking tag is opened.
- The box for “Include user-provided data from your website” is checked.
- The newly created User-Provided Data variable is selected from the dropdown menu.
The setup must be tested using GTM Preview Mode. When the conversion tag fires, the network request can be inspected to verify that the em (email) parameter is populated with a hashed string, confirming a successful, privacy-compliant implementation.
4 comments
Useful walkthrough — particularly the note that the hashing happens inside the variable, since half the implementations out there still ship a hand-written SHA-256 in a custom template.
Method B is where our setup struggles. On the confirmation page the email arrives from an XHR after render, so the DOM variable resolves to an empty string when the tag fires. Is there an ordering guarantee I am missing, or is the DataLayer the only dependable route?
There is no ordering guarantee. A DOM variable is read at the moment the tag fires and never re-read; a value that arrives afterwards is simply not part of that request.
The fix is not a better selector but a better trigger: fire the conversion on the event that carries the data — the same push that signals the purchase — rather than on the page view that happens to precede it. Where the data layer cannot be touched at all, a custom event pushed by the code that renders the address serves the same purpose.
The reason this is worth chasing rather than accepting: an empty email field does not break anything visible. The tag fires, the conversion is counted, and only the enhancement is missing — so the conversion count looks healthy while match rates stay flat. The check that separates the two is the network request itself: the
emparameter is either present with a 64-character hash or it is absent.Coming back to this after implementing it — the note that no manual hashing is required saved an argument with our developers.
One thing I could not settle from the documentation: does the variable normalise before hashing? We also hash the same addresses in the CRM for a different platform,and I would like to be sure the two sides produce identical digests.
The variable normalises the documented fields — trimming whitespace and lowercasing an email address before it hashes. The risk therefore does not sit in the browser; it sits on the other side, in the CRM.
Hashing is not case-insensitive, so
Anna@Example.comandanna@example.comproduce two completely unrelated digests. A CRM export that skips the lowercase step still hashes successfully, still uploads successfully, and matches nothing. No error appears at any stage — the only symptom is a match rate that sits near zero and gets blamed on the platform.Where two systems hash the same identifier, the normalisation rule belongs in a written specification rather than in each implementation’s habits: lowercase, trim, and one documented decision each for plus-addressing and phone number formatting. Those last two are not covered by the platform documentation, which is exactly why they have to be settled once instead of independently on both sides.