Enhanced Conversions

Using the Google Ads API, you can leverage enhanced conversions by sending first-party customer data in the form of conversion adjustments. Google uses this additional data to improve the reporting of your online ad-initiated conversions.

As with other requests to the ConversionAdjustmentUploadService, you can only upload enhanced conversions using the account that manages conversion actions.

In addition, you must complete the setup and configuration steps in that account, including confirming that you have accepted the customer data terms. You can confirm this using the Google Ads UI, but if you'd prefer to use the Google Ads API, retrieve the conversion_tracking_setting from the account that manages conversion actions, using the searchStream or search method of GoogleAdsService and the following query:

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

Then verify that accepted_customer_data_terms is true.

Enhanced conversion as a conversion adjustment

The process for sending data through enhanced conversions is similar to other conversion adjustments, but keep the following key differences in mind when populating the ConversionAdjustment:

  • An order_id is required.
  • The adjustment_type must be ENHANCEMENT.
  • The conversion_action is the resource name of a ConversionAction with a type of WEBPAGE.
  • The user_identifiers collection must contain between one and five identifiers.
  • The user_identifier_source of each identifier is optional.
  • A gclid_date_time_pair with a conversion_date_time is optional but recommended. Set this to the date and time at which the conversion with the specified order_id occurred. Include the timezone offset, and use the format yyyy-mm-dd HH:mm:ss+|-HH:mm, for example: 2022-01-01 19:32:45-05:00 (ignoring daylight saving time) .

    Setting the gclid of the gclid_date_time_pair is also optional.

  • The user_agent string is optional but recommended. This should match the user agent of the request that sent the original conversion so the conversion and its enhancement are either both attributed as same-device or both attributed as cross-device.

  • Any restatement_value is ignored. To restate the value for a conversion, send a separate operation with adjustment_type set to RESTATEMENT. See the adjustments guide for more information.

An enhanced conversion should be uploaded within 24 hours of the original conversion. We recommend uploading at least several minutes before the end of the 24-hour period for a margin of safety from differences between system clocks.

Normalization and hashing

For privacy concerns, email addresses, phone numbers, first names, last names, and street addresses must be hashed using the SHA-256 algorithm before being uploaded. In order to standardize the hash results, prior to hashing one of these values you must:

  • Remove leading/trailing whitespaces.
  • Convert the text to lowercase.
  • Format phone numbers according to the E164 standard.
  • Remove all periods (.) that precede the domain name in gmail.com and googlemail.com email addresses.

Java

private String normalizeAndHash(MessageDigest digest, String s)
    throws UnsupportedEncodingException {
  // Normalizes by removing leading and trailing whitespace and converting all characters to
  // lower case.
  String normalized = s.trim().toLowerCase();
  // 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
{
    return hash($hashAlgorithm, strtolower(trim($value)));
}

/**
 * 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);
}
      

Python

def normalize_and_hash_email_address(email_address):
    """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"

    Args:
        email_address: An email address to normalize.

    Returns:
        A normalized (lowercase, removed whitespace) and SHA-265 hashed string.
    """
    normalized_email = email_address.lower()
    email_parts = normalized_email.split("@")
    # Checks whether the domain of the email address is either "gmail.com"
    # or "googlemail.com". If this regex does not match then this statement
    # will evaluate to None.
    is_gmail = re.match(r"^(gmail|googlemail)\.com$", email_parts[1])

    # Check that there are at least two segments and the second segment
    # matches the above regex expression validating the email domain name.
    if len(email_parts) > 1 and is_gmail:
        # Removes any '.' characters from the portion of the email address
        # before the domain if the domain is gmail.com or googlemail.com.
        email_parts[0] = email_parts[0].replace(".", "")
        normalized_email = "@".join(email_parts)

    return normalize_and_hash(normalized_email)


def normalize_and_hash(s):
    """Normalizes and hashes a string with SHA-256.

    Private customer data must be hashed during upload, as described at:
    https://support.google.com/google-ads/answer/7474263

    Args:
        s: The string to perform this operation on.

    Returns:
        A normalized (lowercase, removed whitespace) and SHA-256 hashed string.
    """
    return hashlib.sha256(s.strip().lower().encode()).hexdigest()
      

Ruby

# Returns the result of normalizing and then hashing the string using the
# provided digest.  Private customer data must be hashed during upload, as
# described at https://support.google.com/google-ads/answer/7474263.
def normalize_and_hash(str)
  # Remove leading and trailing whitespace and ensure all letters are lowercase
  # before hasing.
  Digest::SHA256.hexdigest(str.strip.downcase)
end

# 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'.
def normalize_and_hash_email(email)
  email_parts = email.downcase.split("@")
  # Removes any '.' characters from the portion of the email address before the
  # domain if the domain is gmail.com or googlemail.com.
  if email_parts.last =~ /^(gmail|googlemail)\.com\s*/
    email_parts[0] = email_parts[0].gsub('.', '')
  end
  normalize_and_hash(email_parts.join('@'))
end
      

Perl

sub normalize_and_hash {
  my $value = shift;

  $value =~ s/^\s+|\s+$//g;
  return sha256_hex(lc $value);
}

# 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'.
sub normalize_and_hash_email_address {
  my $email_address = shift;

  my $normalized_email = lc $email_address;
  my @email_parts      = split('@', $normalized_email);
  if (scalar @email_parts > 1
    && $email_parts[1] =~ /^(gmail|googlemail)\.com\s*/)
  {
    # Remove any '.' characters from the portion of the email address before the
    # domain if the domain is 'gmail.com' or 'googlemail.com'.
    $email_parts[0] =~ s/\.//g;
    $normalized_email = sprintf '%s@%s', $email_parts[0], $email_parts[1];
  }
  return normalize_and_hash($normalized_email);
}
      

Building the enhancement adjustment

The following snippet demonstrates how to construct an enhancement adjustment that contains identifiers for email address and user address, with standardization and hashing applied as required.

Java

// Creates a builder for constructing the enhancement adjustment.
ConversionAdjustment.Builder enhancementBuilder =
    ConversionAdjustment.newBuilder()
        .setConversionAction(ResourceNames.conversionAction(customerId, conversionActionId))
        .setAdjustmentType(ConversionAdjustmentType.ENHANCEMENT)
        // Enhancements MUST use order ID instead of GCLID date/time pair.
        .setOrderId(orderId);

// Sets the conversion date and time if provided. Providing this value is optional but
// recommended.
if (conversionDateTime != null) {
  enhancementBuilder.setGclidDateTimePair(
      GclidDateTimePair.newBuilder().setConversionDateTime(conversionDateTime));
}

// Creates a SHA256 message digest for hashing user identifiers in a privacy-safe way, as
// described at https://support.google.com/google-ads/answer/9888656.
MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");

// Adds user identifiers, hashing where required.

// Creates a user identifier using sample values for the user address.
UserIdentifier addressIdentifier =
    UserIdentifier.newBuilder()
        .setAddressInfo(
            OfflineUserAddressInfo.newBuilder()
                .setHashedFirstName(normalizeAndHash(sha256Digest, "Joanna"))
                .setHashedLastName(normalizeAndHash(sha256Digest, "Smith"))
                .setHashedStreetAddress(
                    normalizeAndHash(sha256Digest, "1600 Amphitheatre Pkwy"))
                .setCity("Mountain View")
                .setState("CA")
                .setPostalCode("94043")
                .setCountryCode("US"))
        // Optional: Specifies the user identifier source.
        .setUserIdentifierSource(UserIdentifierSource.FIRST_PARTY)
        .build();

// Creates a user identifier using the hashed email address.
UserIdentifier emailIdentifier =
    UserIdentifier.newBuilder()
        .setUserIdentifierSource(UserIdentifierSource.FIRST_PARTY)
        // Uses the normalize and hash method specifically for email addresses.
        .setHashedEmail(normalizeAndHashEmailAddress(sha256Digest, "joannasmith@example.com"))
        .build();

// Adds the user identifiers to the enhancement adjustment.
enhancementBuilder.addUserIdentifiers(addressIdentifier).addUserIdentifiers(emailIdentifier);

// Sets optional fields where a value was provided.

if (userAgent != null) {
  // Sets the user agent. This should match the user agent of the request that sent the original
  // conversion so the conversion and its enhancement are either both attributed as same-device
  // or both attributed as cross-device.
  enhancementBuilder.setUserAgent(userAgent);
}
      

C#

// Creates the enhancement adjustment.
ConversionAdjustment enhancement = new ConversionAdjustment()
{
    ConversionAction = ResourceNames.ConversionAction(customerId, conversionActionId),
    AdjustmentType = ConversionAdjustmentType.Enhancement,

    // Enhancements MUST use order ID instead of GCLID date/time pair.
    OrderId = orderId
};

// Sets the conversion date and time if provided. Providing this value is optional but
// recommended.
if (string.IsNullOrEmpty(conversionDateTime))
{
    enhancement.GclidDateTimePair = new GclidDateTimePair()
    {
        ConversionDateTime = conversionDateTime
    };
}

// Adds user identifiers, hashing where required.

// Creates a user identifier using sample values for the user address.
UserIdentifier addressIdentifier = new UserIdentifier()
{
    AddressInfo = new OfflineUserAddressInfo()
    {
        HashedFirstName = NormalizeAndHash("Joanna"),
        HashedLastName = NormalizeAndHash("Smith"),
        HashedStreetAddress = NormalizeAndHash("1600 Amphitheatre Pkwy"),
        City = "Mountain View",
        State = "CA",
        PostalCode = "94043",
        CountryCode = "US"
    },
    // Optional: Specifies the user identifier source.
    UserIdentifierSource = UserIdentifierSource.FirstParty
};

// Creates a user identifier using the hashed email address.
UserIdentifier emailIdentifier = new UserIdentifier()
{
    UserIdentifierSource = UserIdentifierSource.FirstParty,
    // Uses the normalize and hash method specifically for email addresses.
    HashedEmail = NormalizeAndHashEmailAddress("joannasmith@example.com")
};

// Adds the user identifiers to the enhancement adjustment.
enhancement.UserIdentifiers.AddRange(new[] { addressIdentifier, emailIdentifier });

// Sets optional fields where a value was provided.
if (!string.IsNullOrEmpty(userAgent))
{
    // Sets the user agent. This should match the user agent of the request that
    // sent the original conversion so the conversion and its enhancement are either
    // both attributed as same-device or both attributed as cross-device.
    enhancement.UserAgent = userAgent;
}

if (restatementValue != null)
{
    enhancement.RestatementValue = new RestatementValue()
    {
        // Sets the new value of the conversion.
        AdjustedValue = restatementValue.Value
    };
    // Sets the currency of the new value, if provided. Otherwise, the default currency
    // from the conversion action is used, and if that is not set then the account
    // currency is used.
    if (restatementCurrencyCode != null)
    {
        enhancement.RestatementValue.CurrencyCode = restatementCurrencyCode;
    }
}
      

PHP

// Creates the conversion enhancement.
$conversionAdjustment = new ConversionAdjustment([
    'conversion_action' =>
        ResourceNames::forConversionAction($customerId, $conversionActionId),
    'adjustment_type' => ConversionAdjustmentType::ENHANCEMENT,
    // Enhancements must use order ID instead of GCLID date/time pair.
    'order_id' => $orderId
]);

// Uses the SHA-256 hash algorithm for hashing user identifiers in a privacy-safe way, as
// described at https://support.google.com/google-ads/answer/9888656.
$hashAlgorithm = "sha256";

// Adds user identifiers, hashing where required.

// Creates a user identifier using sample values for the user address.
$addressIdentifier = new UserIdentifier([
    'address_info' => new OfflineUserAddressInfo([
        'hashed_first_name' => self::normalizeAndHash($hashAlgorithm, 'Dana'),
        'hashed_last_name' => self::normalizeAndHash($hashAlgorithm, 'Quinn'),
        'hashed_street_address' => self::normalizeAndHash(
            $hashAlgorithm,
            '1600 Amphitheatre Pkwy'
        ),
        'city' => 'Mountain View',
        'state' => 'CA',
        'postal_code' => '94043',
        'country_code' => 'US'
    ]),
    // Optional: Specifies the user identifier source.
    'user_identifier_source' => UserIdentifierSource::FIRST_PARTY
]);

// Creates a user identifier using the hashed email address.
$emailIdentifier = new UserIdentifier([
    // Uses the normalize and hash method specifically for email addresses.
    'hashed_email' => self::normalizeAndHashEmailAddress(
        $hashAlgorithm,
        'dana@example.com'
    ),
    // Optional: Specifies the user identifier source.
    'user_identifier_source' => UserIdentifierSource::FIRST_PARTY
]);

// Adds the user identifiers to the enhancement adjustment.
$conversionAdjustment->setUserIdentifiers([$addressIdentifier, $emailIdentifier]);

// Sets optional fields where a value was provided.

if ($conversionDateTime !== null) {
    // Sets the conversion date and time if provided. Providing this value is optional but
    // recommended.
    $conversionAdjustment->setGclidDateTimePair(new GclidDateTimePair([
        'conversion_date_time' => $conversionDateTime
    ]));
}

if ($userAgent !== null) {
    // Sets the user agent. This should match the user agent of the request that sent the
    // original conversion so the conversion and its enhancement are either both attributed
    // as same-device or both attributed as cross-device.
    $conversionAdjustment->setUserAgent($userAgent);
}
      

Python

conversion_action_service = client.get_service("ConversionActionService")
conversion_adjustment = client.get_type("ConversionAdjustment")
conversion_adjustment.conversion_action = conversion_action_service.conversion_action_path(
    customer_id, conversion_action_id
)
conversion_adjustment.adjustment_type = (
    client.enums.ConversionAdjustmentTypeEnum.ENHANCEMENT
)
# Enhancements MUST use order ID instead of GCLID date/time pair.
conversion_adjustment.order_id = order_id

# Sets the conversion date and time if provided. Providing this value is
# optional but recommended.
if conversion_date_time:
    conversion_adjustment.gclid_date_time_pair.conversion_date_time = (
        conversion_date_time
    )

# Creates a user identifier using sample values for the user address,
# hashing where required.
address_identifier = client.get_type("UserIdentifier")
address_identifier.address_info.hashed_first_name = normalize_and_hash(
    "Joanna"
)
address_identifier.address_info.hashed_last_name = normalize_and_hash(
    "Joanna"
)
address_identifier.address_info.hashed_street_address = normalize_and_hash(
    "1600 Amphitheatre Pkwy"
)
address_identifier.address_info.city = "Mountain View"
address_identifier.address_info.state = "CA"
address_identifier.address_info.postal_code = "94043"
address_identifier.address_info.country_code = "US"
# Optional: Specifies the user identifier source.
address_identifier.user_identifier_source = (
    client.enums.UserIdentifierSourceEnum.FIRST_PARTY
)

# Creates a user identifier using the hashed email address.
email_identifier = client.get_type("UserIdentifier")
# Optional: Specifies the user identifier source.
email_identifier.user_identifier_source = (
    client.enums.UserIdentifierSourceEnum.FIRST_PARTY
)
# Uses the normalize and hash method specifically for email addresses.
email_identifier.hashed_email = normalize_and_hash_email_address(
    "dana@example.com"
)

# Adds both user identifiers to the conversion adjustment.
conversion_adjustment.user_identifiers.extend(
    [address_identifier, email_identifier]
)

# Sets optional fields where a value was provided
if user_agent:
    # Sets the user agent. This should match the user agent of the request
    # that sent the original conversion so the conversion and its
    # enhancement are either both attributed as same-device or both
    # attributed as cross-device.
    conversion_adjustment.user_agent = user_agent
      

Ruby

enhancement = client.resource.conversion_adjustment do |ca|
  ca.conversion_action = client.path.conversion_action(customer_id, conversion_action_id)
  ca.adjustment_type = :ENHANCEMENT
  ca.order_id = order_id

  # Sets the conversion date and time if provided. Providing this value is
  # optional but recommended.
  unless conversion_date_time.nil?
    ca.gclid_date_time_pair = client.resource.gclid_date_time_pair do |pair|
      pair.conversion_date_time = conversion_date_time
    end
  end

  # Creates a user identifier using sample values for the user address.
  ca.user_identifiers << client.resource.user_identifier do |ui|
    ui.address_info = client.resource.offline_user_address_info do |info|
      # Certain fields must be hashed using SHA256 in order to handle
      # identifiers in a privacy-safe way, as described at
      # https://support.google.com/google-ads/answer/9888656.
      info.hashed_first_name = normalize_and_hash("Joanna")
      info.hashed_last_name = normalize_and_hash("Smith")
      info.hashed_street_address = normalize_and_hash("1600 Amphitheatre Pkwy")
      info.city = "Mountain View"
      info.state = "CA"
      info.postal_code = "94043"
      info.country_code = "US"
    end
    # Optional: Specifies the user identifier source.
    ui.user_identifier_source = :FIRST_PARTY
  end

  # Creates a user identifier using the hashed email address.
  ca.user_identifiers << client.resource.user_identifier do |ui|
    # Uses the normalize and hash method specifically for email addresses.
    ui.hashed_email = normalize_and_hash_email("dana@example.com")
    ui.user_identifier_source = :FIRST_PARTY
  end

  # Sets optional fields where a value was provided.
  unless user_agent.nil?
    # Sets the user agent. This should match the user agent of the request
    # that sent the original conversion so the conversion and its enhancement
    # are either both attributed as same-device or both attributed as
    # cross-device.
    ca.user_agent = user_agent
  end
end
      

Perl

  # Construct the enhancement adjustment.
  my $enhancement =
    Google::Ads::GoogleAds::V13::Services::ConversionAdjustmentUploadService::ConversionAdjustment
    ->new({
      conversionAction =>
        Google::Ads::GoogleAds::V13::Utils::ResourceNames::conversion_action(
        $customer_id, $conversion_action_id
        ),
      adjustmentType => ENHANCEMENT,
      # Enhancements MUST use order ID instead of GCLID date/time pair.
      orderId => $order_id
    });

  # Set the conversion date and time if provided. Providing this value is optional
  # but recommended.
  if (defined $conversion_date_time) {
    $enhancement->{gclidDateTimePair} =
      Google::Ads::GoogleAds::V13::Services::ConversionAdjustmentUploadService::GclidDateTimePair
      ->new({
        conversionDateTime => $conversion_date_time
      });
  }

  # Add user identifiers, hashing where required.

  # Create a user identifier using sample values for the user address.
  my $address_identifier =
    Google::Ads::GoogleAds::V13::Common::UserIdentifier->new({
      addressInfo =>
        Google::Ads::GoogleAds::V13::Common::OfflineUserAddressInfo->new({
          hashedFirstName     => normalize_and_hash("Dana"),
          hashedLastName      => normalize_and_hash("Quinn"),
          hashedStreetAddress => normalize_and_hash("1600 Amphitheatre Pkwy"),
          city                => "Mountain View",
          state               => "CA",
          postalCode          => "94043",
          countryCode         => "US"
        }
        ),
      # Optional: Specify the user identifier source.
      userIdentifierSource => FIRST_PARTY
    });

  # Create a user identifier using the hashed email address.
  my $email_identifier =
    Google::Ads::GoogleAds::V13::Common::UserIdentifier->new({
      userIdentifierSource => FIRST_PARTY,
      # Use the normalize and hash method specifically for email addresses.
      hashedEmail => normalize_and_hash_email_address('dana@example.com')});

  # Add the user identifiers to the enhancement adjustment.
  $enhancement->{userIdentifiers} = [$address_identifier, $email_identifier];

  # Set optional fields where a value was provided.

  if (defined $user_agent) {
    # Set the user agent. This should match the user agent of the request that
    # sent the original conversion so the conversion and its enhancement are
    # either both attributed as same-device or both attributed as cross-device.
    $enhancement->{userAgent} = $user_agent;
  }

  # Upload the enhancement adjustment. Partial failure should always be set to true.
  my $response =
    $api_client->ConversionAdjustmentUploadService()
    ->upload_conversion_adjustments({
      customerId            => $customer_id,
      conversionAdjustments => [$enhancement],
      # Enable partial failure (must be true).
      partialFailure => "true"
    });

  # Print any partial errors returned.
  if ($response->{partialFailureError}) {
    printf "Partial error encountered: '%s'.\n",
      $response->{partialFailureError}{message};
  } else {
    # Print the result.
    my $result = $response->{results}[0];
    printf "Uploaded conversion adjustment of '%s' for order ID '%s'.\n",
      $result->{conversionAction}, $result->{orderId};
  }

  return 1;
}

# Normalizes and hashes a string value.
# Private customer data must be hashed during upload, as described at
# https://support.google.com/google-ads/answer/7474263.
sub normalize_and_hash {
  my $value = shift;

  $value =~ s/^\s+|\s+$//g;
  return sha256_hex(lc $value);
}

# 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'.
sub normalize_and_hash_email_address {
  my $email_address = shift;

  my $normalized_email = lc $email_address;
  my @email_parts      = split('@', $normalized_email);
  if (scalar @email_parts > 1
    && $email_parts[1] =~ /^(gmail|googlemail)\.com\s*/)
  {
    # Remove any '.' characters from the portion of the email address before the
    # domain if the domain is 'gmail.com' or 'googlemail.com'.
    $email_parts[0] =~ s/\.//g;
    $normalized_email = sprintf '%s@%s', $email_parts[0], $email_parts[1];
  }
  return normalize_and_hash($normalized_email);
}

# Don't run the example if the file is being included.
if (abs_path($0) ne abs_path(__FILE__)) {
  return 1;
}

# Get Google Ads Client, credentials will be read from ~/googleads.properties.
my $api_client = Google::Ads::GoogleAds::Client->new();

# By default examples are set to die on any server returned fault.
$api_client->set_die_on_faults(1);

# Parameters passed on the command line will override any parameters set in code.
GetOptions(
  "customer_id=s"          => \$customer_id,
  "conversion_action_id=i" => \$conversion_action_id,
  "order_id=s"             => \$order_id,
  "conversion_date_time=s" => \$conversion_date_time,
  "user_agent=s"           => \$user_agent
);

# Print the help message if the parameters are not initialized in the code nor
# in the command line.
pod2usage(2)
  if not check_params($customer_id, $conversion_action_id, $order_id);

# Call the example.
upload_conversion_enhancement($api_client, $customer_id =~ s/-//gr,
  $conversion_action_id, $order_id, $conversion_date_time, $user_agent);

=pod

=head1 NAME

upload_conversion_enhancement

=head1 DESCRIPTION

Adjusts an existing conversion by supplying user identifiers so Google can
enhance the conversion value.

=head1 SYNOPSIS

upload_conversion_enhancement.pl [options]

    -help                       Show the help message.
    -customer_id                The Google Ads customer ID.
    -conversion_action_id       The conversion action ID associated with this conversion.
    -order_id                   The unique order ID (transaction ID) of the conversion.
    -conversion_date_time       [optional] The date time at which the conversion with the specified order ID
                                occurred. Must be after the click time, and must include the time zone offset.
                                The format is "yyyy-mm-dd hh:mm:ss+|-hh:mm", e.g. "2019-01-01 12:32:45-08:00".
                                Setting this field is optional, but recommended.
    -user_agent                 [optional] The HTTP user agent of the conversion.

=cut

      

Common errors

Use the enhanced conversions API diagnostics report to validate that your enhanced conversions are working effectively.

Here are some common errors that may occur during uploads:

ConversionAdjustmentUploadError.CUSTOMER_NOT_ACCEPTED_CUSTOMER_DATA_TERMS
The customer data terms and conditions have not been accepted for the customer_id of the request.
ConversionAdjustmentUploadError.CONVERSION_ACTION_NOT_ELIGIBLE_FOR_ENHANCEMENT
The conversion_action supplied is not eligible for enhanced conversions. In the Google Ads UI, make sure you check the Turn on enhanced conversions box on the conversion action referenced in your request.
ConversionAdjustmentUploadError.INVALID_USER_IDENTIFIER
A user_identifier for a field that requires hashing was not hashed using the SHA-256 algorithm.
ConversionAdjustmentUploadError.UNSUPPORTED_USER_IDENTIFIER
A user_identifier of the adjustment contains a value that is not one of the allowed identifiers.
CollectionSizeError.TOO_MANY
The user_identifiers collection contains more than five elements.