Manage offline conversions

  • Importing offline conversions using Google Ads API with enhanced conversions for leads improves accuracy by using hashed user data.

  • Implementation involves configuring tagging, normalizing/hashing user data, populating ClickConversion objects with identifiers and details, and importing them via the API.

  • Prerequisites include completing Google Ads API setup and enabling enhanced conversions for leads in Google Ads.

  • Review imports using the diagnostics report, noting that conversions report on the original click's impression date and may take time to appear.

  • Best practices include sending all available data, batching requests, using job_id for diagnostics, and avoiding custom variables with user data.

You can use the Google Ads API to import offline conversions into Google Ads to track ads that led to sales in the offline world, such as over the phone or through a sales rep.

In order to fully take advantage of the benefits of importing conversion data, we recommend implementing enhanced conversions for leads, which leverages GCLIDs and user-provided data to maximize durability and performance.

Enhanced conversions

Enhanced conversions help you to improve the accuracy of your conversion measurement by supplementing your conversions with first-party conversion data, like email address and phone number.

There are two types of enhanced conversions. For more details, see About enhanced conversions.

The following section explains how to enhance offline conversions, a feature also referred to as enhanced conversions for leads.

What is enhanced conversions for leads?

Enhanced conversions for leads is an upgraded version of offline conversion import that uses user-provided data, such as email addresses, to supplement imported offline conversion data to improve accuracy and bidding performance. When you import your offline conversions, the provided hashed customer data is used to attribute back to the Google Ads campaign by matching to the same data collected on your website, such as a, lead form, and to signed-in customers who engaged with your ad. For more information, see About enhanced conversions for leads.

There are two ways to implement enhanced conversions for leads depending on whether you use the Google tag to track form submission events on your webpage. For the best performance and durability, we recommend using Google tag for enhanced conversions for leads.

  • If you are starting from scratch, start with the Prerequisites section.
  • If you already have offline conversion imports set up, and want to upgrade to enhanced conversions for leads, we recommend starting with the Configure tagging section.
  • If you've already set up the Google tag, or don't plan to use the Google tag, and are starting work on your Google Ads API integration, jump to the API Implementation section.
  • If you are unable to import user-provided data, or rely on external attribution for your conversions, see the Legacy Offline Conversion Imports Guide.

Prerequisites

First, verify that you've completed the steps in our getting started section.

You must opt in to enhanced conversions for leads and accept the customer data terms before you can use enhanced conversions for leads. You can verify whether these prerequisites are already met by issuing the following query to the Google Ads conversion customer:

SELECT
  customer.id,
  customer.conversion_tracking_setting.accepted_customer_data_terms,
  customer.conversion_tracking_setting.enhanced_conversions_for_leads_enabled
FROM customer

If either of accepted_customer_data_terms or enhanced_conversions_for_leads_enabled is false, follow the instructions in Create a new conversion action to complete these prerequisites.

Configure tagging

Configure the Google tag to enable enhanced conversions for leads by following the instructions to Configure Google tag settings. To set up enhanced conversions for leads with Google Tag Manager, follow these instructions.

API Implementation

Here is the overall flow for importing enhanced conversions for leads using the Google Ads API.

  1. Normalize and hash the user-provided data, such as email address, and phone number.

  2. Populate ClickConversion objects with the normalized and hashed user-provided data.

  3. Import ClickConversion objects to the Google Ads API using the ConversionUploadService. See UploadClickConversions for more details.

  4. Review your imports.

Retrieve your conversion action resource name

To upload conversions, you must specify the resource name of the ConversionAction you've set up in Google Ads for importing click conversions. You can find the resource name by querying the conversion_action resource using the following GAQL query:

SELECT
  customer.id,
  conversion_action.id,
  conversion_action.name,
  conversion_action.type,
  conversion_action.resource_name
FROM conversion_action
WHERE conversion_action.type = 'UPLOAD_CLICKS'
  AND conversion_action.status = 'ENABLED'

Normalize and hash user-provided data

For privacy considerations, the following data must be hashed using SHA-256 before being imported:

  • Email address
  • Phone number

To standardize the hash results, do the following prior to hashing these values:

  • Remove leading and trailing whitespaces.
  • Convert the text to lowercase.
  • Format phone numbers according to the E164 standard.
  • For email addresses:
    • Remove all periods (.) in the username (before the @ symbol). For example, jane.doe@example.com becomes janedoe@example.com.
    • Remove the plus (+) symbol and any characters following it in the username (before the @ symbol). For example, janedoe+newsletter@example.com becomes janedoe@example.com.

Code example

This example shows how to normalize and hash user-provided data.

Java

private String normalizeAndHash(MessageDigest digest, String s)
    throws UnsupportedEncodingException {
  // Normalizes by first converting all characters to lowercase, then trimming spaces.
  String normalized = s.toLowerCase();
  // Removes leading, trailing, and intermediate spaces.
  normalized = normalized.replaceAll("\\s+", "");
  // Hashes the normalized string using the hashing algorithm.
  byte[] hash = digest.digest(normalized.getBytes("UTF-8"));
  StringBuilder result = new StringBuilder();
  for (byte b : hash) {
    result.append(String.format("%02x", b));
  }

  return result.toString();
}

/**
 * Returns the result of normalizing and hashing an email address. For this use case, Google Ads
 * requires removal of any '.' characters preceding {@code gmail.com} or {@code googlemail.com}.
 *
 * @param digest the digest to use to hash the normalized string.
 * @param emailAddress the email address to normalize and hash.
 */
private String normalizeAndHashEmailAddress(MessageDigest digest, String emailAddress)
    throws UnsupportedEncodingException {
  String normalizedEmail = emailAddress.toLowerCase();
  String[] emailParts = normalizedEmail.split("@");
  if (emailParts.length > 1 && emailParts[1].matches("^(gmail|googlemail)\\.com\\s*")) {
    // Removes any '.' characters from the portion of the email address before the domain if the
    // domain is gmail.com or googlemail.com.
    emailParts[0] = emailParts[0].replaceAll("\\.", "");
    normalizedEmail = String.format("%s@%s", emailParts[0], emailParts[1]);
  }
  return normalizeAndHash(digest, normalizedEmail);
}
      

C#

/// <summary>
/// Normalizes the email address and hashes it. For this use case, Google Ads requires
/// removal of any '.' characters preceding <code>gmail.com</code> or
/// <code>googlemail.com</code>.
/// </summary>
/// <param name="emailAddress">The email address.</param>
/// <returns>The hash code.</returns>
private string NormalizeAndHashEmailAddress(string emailAddress)
{
    string normalizedEmail = emailAddress.ToLower();
    string[] emailParts = normalizedEmail.Split('@');
    if (emailParts.Length > 1 && (emailParts[1] == "gmail.com" ||
        emailParts[1] == "googlemail.com"))
    {
        // Removes any '.' characters from the portion of the email address before
        // the domain if the domain is gmail.com or googlemail.com.
        emailParts[0] = emailParts[0].Replace(".", "");
        normalizedEmail = $"{emailParts[0]}@{emailParts[1]}";
    }
    return NormalizeAndHash(normalizedEmail);
}

/// <summary>
/// Normalizes and hashes a string value.
/// </summary>
/// <param name="value">The value to normalize and hash.</param>
/// <returns>The normalized and hashed value.</returns>
private static string NormalizeAndHash(string value)
{
    return ToSha256String(digest, ToNormalizedValue(value));
}

/// <summary>
/// Hash a string value using SHA-256 hashing algorithm.
/// </summary>
/// <param name="digest">Provides the algorithm for SHA-256.</param>
/// <param name="value">The string value (e.g. an email address) to hash.</param>
/// <returns>The hashed value.</returns>
private static string ToSha256String(SHA256 digest, string value)
{
    byte[] digestBytes = digest.ComputeHash(Encoding.UTF8.GetBytes(value));
    // Convert the byte array into an unhyphenated hexadecimal string.
    return BitConverter.ToString(digestBytes).Replace("-", string.Empty);
}

/// <summary>
/// Removes leading and trailing whitespace and converts all characters to
/// lower case.
/// </summary>
/// <param name="value">The value to normalize.</param>
/// <returns>The normalized value.</returns>
private static string ToNormalizedValue(string value)
{
    return value.Trim().ToLower();
}
      

PHP

private static function normalizeAndHash(string $hashAlgorithm, string $value): string
{
    // Normalizes by first converting all characters to lowercase, then trimming spaces.
    $normalized = strtolower($value);
    // Removes leading, trailing, and intermediate spaces.
    $normalized = str_replace(' ', '', $normalized);
    return hash($hashAlgorithm, strtolower(trim($normalized)));
}

/**
 * Returns the result of normalizing and hashing an email address. For this use case, Google
 * Ads requires removal of any '.' characters preceding "gmail.com" or "googlemail.com".
 *
 * @param string $hashAlgorithm the hash algorithm to use
 * @param string $emailAddress the email address to normalize and hash
 * @return string the normalized and hashed email address
 */
private static function normalizeAndHashEmailAddress(
    string $hashAlgorithm,
    string $emailAddress
): string {
    $normalizedEmail = strtolower($emailAddress);
    $emailParts = explode("@", $normalizedEmail);
    if (
        count($emailParts) > 1
        && preg_match('/^(gmail|googlemail)\.com\s*/', $emailParts[1])
    ) {
        // Removes any '.' characters from the portion of the email address before the domain
        // if the domain is gmail.com or googlemail.com.
        $emailParts[0] = str_replace(".", "", $emailParts[0]);
        $normalizedEmail = sprintf('%s@%s', $emailParts[0], $emailParts[1]);
    }
    return self::normalizeAndHash($hashAlgorithm, $normalizedEmail);
}