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 conversions driven by ad interactions.

You must complete the setup and configuration steps before you can use enhanced conversions in the Google Ads API.

Overview

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

  • order_id is required.
  • adjustment_type must be ENHANCEMENT.
  • conversion_action must be the resource_name of a ConversionAction with a type of WEBPAGE and enhanced conversions enabled.
  • user_identifiers must contain between one and five identifiers. Enhancements support the following identifiers:
    • hashed_email
    • hashed_phone_number
    • address_info
  • user_identifier_source of each identifier is optional.
  • gclid_date_time_pair with 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 optional.

  • user_agent is required. 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.

  • restatement_value with adjusted_value and currency_code are optional but recommended.

In addition, you must upload the enhancement adjustment within 24 hours of the original conversion for the order_id. To avoid errors due to differences in clock times, upload the adjustment no later than several minutes before the end of the 24-hour period.

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

Enhancement adjustments

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

if (restatementValue != null) {
  // Creates a builder to construct the restated conversion value.
  RestatementValue.Builder valueBuilder = enhancementBuilder.getRestatementValueBuilder();
  // Sets the new value of the conversion.
  valueBuilder.setAdjustedValue(restatementValue);
  // 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) {
    valueBuilder.setCurrencyCode(restatementCurrencyCode);
  }
}
      

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, 'Joanna'),
        'hashed_last_name' => self::normalizeAndHash($hashAlgorithm, 'Smith'),
        '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,
        'joannasmith@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);
}

if ($restatementValue !== null) {
    // Sets the new value of the conversion.
    $restatementValue = new RestatementValue([
        'adjusted_value' => $restatementValue
    ]);
    // 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) {
        $restatementValue->setCurrencyCode($restatementCurrencyCode);
    }
    $conversionAdjustment->setRestatementValue($restatementValue);
}
      

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(
        "joannasmith@gmail.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

    if restatement_value:
        # Sets the new value of the conversion.
        conversion_adjustment.restatement_value.adjusted_value = (
            restatement_value
        )
        if currency_code:
            # 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.
            conversion_adjustment.restatement_value.currency_code = (
                currency_code
            )

    # Creates the conversion adjustment upload service client.
    conversion_adjustment_upload_service = client.get_service(
        "ConversionAdjustmentUploadService"
    )
    # Uploads the enhancement adjustment. Partial failure should always be set
    # to true.
    response = conversion_adjustment_upload_service.upload_conversion_adjustments(
        customer_id=customer_id,
        conversion_adjustments=[conversion_adjustment],
        # Enables partial failure (must be true).
        partial_failure=True,
    )

    # Prints any partial errors returned.
    if response.partial_failure_error:
        print(
            "Partial error encountered: "
            f"{response.partial_failure_error.message}"
        )

    # Prints the result.
    result = response.results[0]
    # Only prints valid results. If the click conversion failed then this
    # result will be returned as an empty message and will be falsy.
    if result:
        print(
            f"Uploaded conversion adjustment of {result.conversion_action} for "
            f"order ID {result,order_id}."
        )


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()


if __name__ == "__main__":
    # GoogleAdsClient will read the google-ads.yaml configuration file in the
    # home directory if none is specified.
    googleads_client = GoogleAdsClient.load_from_storage(version="v10")

    parser = argparse.ArgumentParser(
        description="Imports offline call conversion values for calls related "
        "to your ads."
    )
    # The following argument(s) should be provided to run the example.
    parser.add_argument(
        "-c",
        "--customer_id",
        type=str,
        required=True,
        help="The Google Ads customer ID.",
    )
    parser.add_argument(
        "-a",
        "--conversion_action_id",
        type=str,
        required=True,
        help="The ID of the conversion action to upload to.",
    )
    parser.add_argument(
        "-o",
        "--order_id",
        type=str,
        required=True,
        help="the unique ID (transaction ID) of the conversion.",
    )
    parser.add_argument(
        "-d",
        "--conversion_date_time",
        type=str,
        help="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",
    )
    parser.add_argument(
        "-u",
        "--user_agent",
        type=str,
        help="The HTTP user agent of the conversion.",
    )
    parser.add_argument(
        "-v",
        "--restatement_value",
        type=float,
        help="The enhancement value.",
    )
    parser.add_argument(
        "-y",
        "--currency_code",
        type=str,
        required=True,
        help="The currency of the conversion value.",
    )
    args = parser.parse_args()

    try:
        main(
            googleads_client,
            args.customer_id,
            args.conversion_action_id,
            args.order_id,
            args.conversion_date_time,
            args.user_agent,
            args.restatement_value,
            args.currency_code,
        )
    except GoogleAdsException as ex:
        print(
            f"Request with ID '{ex.request_id}'' failed with status "
            f"'{ex.error.code().name}' and includes the following errors:"
        )
        for error in ex.failure.errors:
            print(f"\tError with message '{error.message}'.")
            if error.location:
                for field_path_element in error.location.field_path_elements:
                    print(f"\t\tOn field: {field_path_element.field_name}")
        sys.exit(1)

      

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("joannasmith@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

  unless restatement_value.nil?
    ca.restatement_value = client.resource.restatement_value do |ra|
      ra.adjusted_value = restatement_value.to_f
      # 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.
      unless currency_code.nil?
        ra.currency_code = currency_code
      end
    end
  end
end
      

Perl

# Construct the enhancement adjustment.
my $enhancement =
  Google::Ads::GoogleAds::V10::Services::ConversionAdjustmentUploadService::ConversionAdjustment
  ->new({
    conversionAction =>
      Google::Ads::GoogleAds::V10::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::V10::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::V10::Common::UserIdentifier->new({
    addressInfo =>
      Google::Ads::GoogleAds::V10::Common::OfflineUserAddressInfo->new({
        hashedFirstName     => normalize_and_hash("Joanna"),
        hashedLastName      => normalize_and_hash("Smith"),
        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::V10::Common::UserIdentifier->new({
    userIdentifierSource => FIRST_PARTY,
    # Use the normalize and hash method specifically for email addresses.
    hashedEmail => normalize_and_hash_email_address('joannasmith@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;
}

if (defined $restatement_value) {
  # Construct the restated conversion value.
  my $value =
    Google::Ads::GoogleAds::V10::Services::ConversionAdjustmentUploadService::RestatementValue
    ->new({
      # Set the new value of the conversion.
      adjustedValue => $restatement_value
    });

  # Set 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 (defined $currency_code) {
    $value->{currencyCode} = $currency_code;
  }

  $enhancement->{restatementValue} = $value;
}
      

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.