Upload Enhanced Conversions for Leads

Java

// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.ads.googleads.examples.remarketing;

import com.beust.jcommander.Parameter;
import com.google.ads.googleads.examples.utils.ArgumentNames;
import com.google.ads.googleads.examples.utils.CodeSampleParams;
import com.google.ads.googleads.lib.GoogleAdsClient;
import com.google.ads.googleads.v16.common.Consent;
import com.google.ads.googleads.v16.common.UserIdentifier;
import com.google.ads.googleads.v16.enums.ConsentStatusEnum.ConsentStatus;
import com.google.ads.googleads.v16.enums.UserIdentifierSourceEnum.UserIdentifierSource;
import com.google.ads.googleads.v16.errors.GoogleAdsError;
import com.google.ads.googleads.v16.errors.GoogleAdsException;
import com.google.ads.googleads.v16.services.ClickConversion;
import com.google.ads.googleads.v16.services.ClickConversionResult;
import com.google.ads.googleads.v16.services.ConversionUploadServiceClient;
import com.google.ads.googleads.v16.services.UploadClickConversionsRequest;
import com.google.ads.googleads.v16.services.UploadClickConversionsResponse;
import com.google.ads.googleads.v16.utils.ResourceNames;
import com.google.common.collect.ImmutableMap;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Uploads an enhanced conversion for leads by uploading a ClickConversion with hashed, first-party
 * user-provided data from your website lead forms. This includes user identifiers, and optionally a
 * click ID and order ID. With this information, Google can tie the conversion to the ad that drove
 * the lead.
 */
public class UploadEnhancedConversionsForLeads {
  private static class UploadEnhancedConversionsForLeadsParams extends CodeSampleParams {

    @Parameter(names = ArgumentNames.CUSTOMER_ID, required = true)
    private long customerId;

    @Parameter(names = ArgumentNames.CONVERSION_ACTION_ID, required = true)
    private long conversionActionId;

    @Parameter(
        names = ArgumentNames.CONVERSION_DATE_TIME,
        required = true,
        description =
            "The date time at which the conversion 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'.")
    private String conversionDateTime;

    @Parameter(names = ArgumentNames.CONVERSION_VALUE, required = true)
    private Double conversionValue;

    @Parameter(names = ArgumentNames.ORDER_ID, required = false)
    private String orderId;

    @Parameter(names = ArgumentNames.GCLID, required = false)
    private String gclid;

    @Parameter(names = ArgumentNames.AD_USER_DATA_CONSENT, required = false)
    private ConsentStatus adUserDataConsent;
  }

  public static void main(String[] args)
      throws UnsupportedEncodingException, NoSuchAlgorithmException {
    UploadEnhancedConversionsForLeadsParams params = new UploadEnhancedConversionsForLeadsParams();
    if (!params.parseArguments(args)) {

      // Either pass the required parameters for this example on the command line, or insert them
      // into the code here. See the parameter class definition above for descriptions.
      params.customerId = Long.parseLong("INSERT_CUSTOMER_ID_HERE");
      params.conversionActionId = Long.parseLong("INSERT_CONVERSION_ACTION_ID_HERE");
      params.conversionDateTime = "INSERT_CONVERSION_DATE_TIME_HERE";
      params.conversionValue = Double.parseDouble("INSERT_CONVERSION_VALUE_HERE");
      // Optional: Specify the unique order ID for the click conversion.
      params.orderId = null;
      // Optional: specify the Google click ID (gclid) for the click.
      params.gclid = null;
      // Optional: specify the ad user data consent for the click.
      params.adUserDataConsent = null;
    }

    GoogleAdsClient googleAdsClient = null;
    try {
      googleAdsClient = GoogleAdsClient.newBuilder().fromPropertiesFile().build();
    } catch (FileNotFoundException fnfe) {
      System.err.printf(
          "Failed to load GoogleAdsClient configuration from file. Exception: %s%n", fnfe);
      System.exit(1);
    } catch (IOException ioe) {
      System.err.printf("Failed to create GoogleAdsClient. Exception: %s%n", ioe);
      System.exit(1);
    }

    try {
      new UploadEnhancedConversionsForLeads()
          .runExample(
              googleAdsClient,
              params.customerId,
              params.conversionActionId,
              params.conversionDateTime,
              params.conversionValue,
              params.orderId,
              params.gclid,
              params.adUserDataConsent);
    } catch (GoogleAdsException gae) {
      // GoogleAdsException is the base class for most exceptions thrown by an API request.
      // Instances of this exception have a message and a GoogleAdsFailure that contains a
      // collection of GoogleAdsErrors that indicate the underlying causes of the
      // GoogleAdsException.
      System.err.printf(
          "Request ID %s failed due to GoogleAdsException. Underlying errors:%n",
          gae.getRequestId());
      int i = 0;
      for (GoogleAdsError googleAdsError : gae.getGoogleAdsFailure().getErrorsList()) {
        System.err.printf("  Error %d: %s%n", i++, googleAdsError);
      }
      System.exit(1);
    }
  }

  /**
   * Runs the example.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param conversionActionId conversion action ID associated with this conversion.
   * @param conversionDateTime date and time of the conversion.
   * @param conversionValue the value of the conversion.
   * @param orderId the unique ID (transaction ID) of the conversion.
   * @param gclid the Google click ID of the conversion.
   * @param adUserDataConsent the ad user data consent for the click
   */
  private void runExample(
      GoogleAdsClient googleAdsClient,
      long customerId,
      long conversionActionId,
      String conversionDateTime,
      Double conversionValue,
      String orderId,
      String gclid,
      ConsentStatus adUserDataConsent)
      throws UnsupportedEncodingException, NoSuchAlgorithmException {
    // Creates an empty builder for constructing the click conversion.
    ClickConversion.Builder clickConversionBuilder = ClickConversion.newBuilder();

    // Extracts user email and phone from the raw data, normalizes and hashes it, then wraps it in
    // UserIdentifier objects.
    // Creates a separate UserIdentifier object for each. The data in this example is hardcoded, but
    // in your application you might read the raw data from an input file.

    // IMPORTANT: Since the identifier attribute of UserIdentifier
    // (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier) is a
    // oneof
    // (https://protobuf.dev/programming-guides/proto3/#oneof-features), you must set only ONE of
    // hashedEmail, hashedPhoneNumber, mobileId, thirdPartyUserId, or addressInfo. Setting more
    // than one of these attributes on the same UserIdentifier will clear all the other members
    // of the oneof. For example, the following code is INCORRECT and will result in a
    // UserIdentifier with ONLY a hashedPhoneNumber.
    //
    // UserIdentifier incorrectlyPopulatedUserIdentifier =
    //     UserIdentifier.newBuilder()
    //         .setHashedEmail("...")
    //         .setHashedPhoneNumber("...")
    //         .build();

    Map<String, String> rawRecord =
        ImmutableMap.<String, String>builder()
            // Email address that includes a period (.) before the Gmail domain.
            .put("email", "alex.2@example.com")
            // Phone number to be converted to E.164 format, with a leading '+' as required.
            .put("phone", "+1 800 5550102")
            // This example lets you input conversion details as arguments, but in reality you might
            // store this data alongside other user data, so we include it in this sample user
            // record.
            .put("orderId", orderId)
            .put("gclid", gclid)
            .put("conversionActionId", Long.toString(conversionActionId))
            .put("conversionDateTime", conversionDateTime)
            .put("conversionValue", Double.toString(conversionValue))
            .put("currencyCode", "USD")
            .put("adUserDataConsent", adUserDataConsent == null ? null : adUserDataConsent.name())
            .build();

    // 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");

    // Creates a list for the user identifiers.
    List<UserIdentifier> userIdentifiers = new ArrayList<>();

    // Creates a user identifier using the hashed email address, using the normalize and hash method
    // specifically for email addresses.
    UserIdentifier emailIdentifier =
        UserIdentifier.newBuilder()
            // Optional: specify the user identifier source.
            .setUserIdentifierSource(UserIdentifierSource.FIRST_PARTY)
            // Uses the normalize and hash method specifically for email addresses.
            .setHashedEmail(normalizeAndHashEmailAddress(sha256Digest, rawRecord.get("email")))
            .build();
    userIdentifiers.add(emailIdentifier);

    // Creates a user identifier using normalized and hashed phone info.
    UserIdentifier hashedPhoneNumberIdentifier =
        UserIdentifier.newBuilder()
            .setHashedPhoneNumber(normalizeAndHash(sha256Digest, rawRecord.get("phone")))
            .build();
    // Adds the hashed phone number identifier to the UserData object's list.
    userIdentifiers.add(hashedPhoneNumberIdentifier);

    // Adds the user identifiers to the conversion.
    clickConversionBuilder.addAllUserIdentifiers(userIdentifiers);

    // Adds details of the conversion.
    clickConversionBuilder.setConversionAction(
        ResourceNames.conversionAction(
            customerId, Long.parseLong(rawRecord.get("conversionActionId"))));
    clickConversionBuilder.setConversionDateTime(rawRecord.get("conversionDateTime"));
    clickConversionBuilder.setConversionValue(Double.parseDouble(rawRecord.get("conversionValue")));
    clickConversionBuilder.setCurrencyCode(rawRecord.get("currencyCode"));

    // Sets the order ID if provided.
    if (rawRecord.get("orderId") != null) {
      clickConversionBuilder.setOrderId(rawRecord.get("orderId"));
    }

    // Sets the Google click ID (gclid) if provided.
    if (rawRecord.get("gclid") != null) {
      clickConversionBuilder.setGclid(rawRecord.get("gclid"));
    }

    // Sets the consent information, if provided.
    if (rawRecord.get("adUserDataConsent") != null) {
      // Specifies whether user consent was obtained for the data you are uploading. See
      // https://www.google.com/about/company/user-consent-policy for details.
      clickConversionBuilder.setConsent(
          Consent.newBuilder()
              .setAdUserData(ConsentStatus.valueOf(rawRecord.get("adUserDataConsent"))));
    }

    // Calls build to build the conversion.
    ClickConversion clickConversion = clickConversionBuilder.build();

    // Creates the conversion upload service client.
    try (ConversionUploadServiceClient conversionUploadServiceClient =
        googleAdsClient.getLatestVersion().createConversionUploadServiceClient()) {
      // Uploads the click conversion. Partial failure should always be set to true.

      // NOTE: This request contains a single conversion as a demonstration.  However, if you have
      // multiple conversions to upload, it's best to upload multiple conversions per request
      // instead of sending a separate request per conversion. See the following for per-request
      // limits:
      // https://developers.google.com/google-ads/api/docs/best-practices/quotas#conversion_upload_service
      UploadClickConversionsResponse response =
          conversionUploadServiceClient.uploadClickConversions(
              UploadClickConversionsRequest.newBuilder()
                  .setCustomerId(Long.toString(customerId))
                  .addConversions(clickConversion)
                  // Enables partial failure (must be true).
                  .setPartialFailure(true)
                  .build());

      // Prints any partial errors returned.
      // To review the overall health of your recent uploads, see:
      // https://developers.google.com/google-ads/api/docs/conversions/upload-summaries
      if (response.hasPartialFailureError()) {
        System.out.printf(
            "Partial error encountered: '%s'.%n", response.getPartialFailureError().getMessage());
      }

      // Prints the result.
      ClickConversionResult result = response.getResults(0);
      // Only prints valid results.
      if (result.hasConversionDateTime()) {
        System.out.printf(
            "Uploaded conversion that occurred at '%s' to '%s'.%n",
            result.getConversionDateTime(), result.getConversionAction());
      }
    }
  }

  /**
   * 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.
   *
   * @param digest the digest to use to hash the normalized string.
   * @param s the string to normalize and hash.
   */
  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#

// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using CommandLine;
using Google.Ads.Gax.Examples;
using Google.Ads.GoogleAds.Lib;
using Google.Ads.GoogleAds.V16.Common;
using Google.Ads.GoogleAds.V16.Errors;
using Google.Ads.GoogleAds.V16.Services;
using System;
using System.Security.Cryptography;
using System.Text;
using static Google.Ads.GoogleAds.V16.Enums.ConsentStatusEnum.Types;
using static Google.Ads.GoogleAds.V16.Enums.UserIdentifierSourceEnum.Types;

namespace Google.Ads.GoogleAds.Examples.V16
{
    /// <summary>
    /// This code example uploads an enhanced conversion for leads by uploading a ClickConversion
    /// with user identifiers, and optionally a click ID and order ID, so Google can more accurately
    /// tie the conversion to the ad that drove the lead.
    /// </summary>
    public class UploadEnhancedConversionsForLeads : ExampleBase
    {
        /// <summary>
        /// Command line options for running the <see cref="UploadEnhancedConversionsForLeads"/> example.
        /// </summary>
        public class Options : OptionsBase
        {
            /// <summary>
            /// The Google Ads customer ID for which conversions are uploaded.
            /// </summary>
            [Option("customerId", Required = true, HelpText =
                "The Google Ads customer ID for which conversions are uploaded.")]
            public long CustomerId { get; set; }

            /// <summary>
            /// ID of the conversion action for which conversions are uploaded.
            /// </summary>
            [Option("conversionActionId", Required = true, HelpText =
                "ID of the conversion action for which conversions are uploaded.")]
            public long ConversionActionId { get; set; }

            /// <summary>
            /// The date time at which the conversion 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'.
            /// </summary>
            [Option("conversionDateTime", Required = true, HelpText =
                "The date time at which the conversion 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'.")]
            public string ConversionDateTime { get; set; }

            /// <summary>
            /// The conversion value.
            /// </summary>
            [Option("conversionValue", Required = true, HelpText =
                "The conversion value.")]
            public double ConversionValue { get; set; }

            /// <summary>
            /// The unique order ID (transaction ID) of the conversion.
            /// </summary>
            [Option("orderId", Required = false, HelpText =
                "The unique order ID (transaction ID) of the conversion.")]
            public string OrderId { get; set; }

            /// <summary>
            /// The Google click ID.
            /// </summary>
            [Option("gclid", Required = false, HelpText =
                "The Google click ID.")]
            public string Gclid { get; set; }
        }

        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            Options options = ExampleUtilities.ParseCommandLine<Options>(args);

            UploadEnhancedConversionsForLeads codeExample = new UploadEnhancedConversionsForLeads();
            Console.WriteLine(codeExample.Description);

            codeExample.Run(new GoogleAdsClient(), options.CustomerId, options.ConversionActionId,
                options.ConversionDateTime, options.ConversionValue,
                options.OrderId, options.Gclid);
        }

        private static SHA256 digest = SHA256.Create();

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description =>
            "This code example uploads an enhanced conversion for leads by uploading a " +
            "ClickConversion with user identifiers, and optionally a click ID and order ID, so " +
            "Google can more accurately tie the conversion to the ad that drove the lead.";

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The Google Ads customer ID for which conversions are uploaded.
        /// </param>
        /// <param name="conversionActionId">ID of the conversion action for which conversions are
        /// uploaded.</param>
        /// <param name="conversionDateTime">The date time at which the conversion occurred.</param>
        /// <param name="conversionValue">The conversion value.</param>
        /// <param name="orderId">The unique order ID (transaction ID) of the conversion.</param>
        /// <param name="gclid">The Google click ID</param>
        public void Run(GoogleAdsClient client, long customerId, long conversionActionId,
            string conversionDateTime, double conversionValue, string orderId, string gclid)
        {
            // Get the ConversionUploadService.
            ConversionUploadServiceClient conversionUploadService =
                client.GetService(Services.V16.ConversionUploadService);

            // Creates an empty click conversion.
            ClickConversion clickConversion = new ClickConversion();

            // Gets the conversion action resource name.
            /*string conversionActionResourceName =


            // Creates a builder for constructing the click conversion.
            ClickConversion clickConversion = new ClickConversion()
            {
                ConversionAction = conversionActionResourceName,
                ConversionDateTime = conversionDateTime,
                ConversionValue = conversionValue,
                CurrencyCode = "USD",
                // Specifies whether user consent was obtained for the data you are uploading. See
                // https://www.google.com/about/company/user-consent-policy
                // for details.
                Consent = new Consent()
                {
                    AdPersonalization = ConsentStatus.Granted,
                    AdUserData = ConsentStatus.Denied
                }
            };

            // Sets the order ID if provided.
            if (!string.IsNullOrEmpty(orderId))
            {
                clickConversion.OrderId = orderId;
            }*/

            // Adds a user identifier using the hashed email address, using the normalize
            // and hash method specifically for email addresses.
            clickConversion.UserIdentifiers.Add(new UserIdentifier()
            {
                HashedEmail = NormalizeAndHashEmailAddress("alex.2@example.com"),
                // Optional: Specifies the user identifier source.
                UserIdentifierSource = UserIdentifierSource.FirstParty
            });

            // Adds a user identifier using normalized and hashed phone info.
            clickConversion.UserIdentifiers.Add(new UserIdentifier()
            {
                HashedPhoneNumber = NormalizeAndHash("+1 800 5550102"),
                // Optional: Specifies the user identifier source.
                UserIdentifierSource = UserIdentifierSource.FirstParty
            });

            // Adds a user identifier with all the required mailing address elements.
            clickConversion.UserIdentifiers.Add(new UserIdentifier()
            {
                AddressInfo = new OfflineUserAddressInfo()
                {
                    // FirstName and LastName must be normalized and hashed.
                    HashedFirstName = NormalizeAndHash("Alex"),
                    HashedLastName = NormalizeAndHash("Quinn"),
                    // CountryCode and PostalCode are sent in plain text.
                    CountryCode = "US",
                    PostalCode = "94045"
                }
            });


            // Adds details of the conversion.
            clickConversion.ConversionAction =
                ResourceNames.ConversionAction(customerId, conversionActionId);
            clickConversion.ConversionDateTime = conversionDateTime;
            clickConversion.ConversionValue = conversionValue;
            clickConversion.CurrencyCode = "USD";

            // Sets the order ID if provided.
            if (!string.IsNullOrEmpty(orderId))
            {
                clickConversion.OrderId = orderId;
            }

            // Sets the Google click ID (gclid) if provided.
            if (!string.IsNullOrEmpty(gclid))
            {
                clickConversion.Gclid = gclid;
            }


            try
            {
                // Uploads the click conversion. Partial failure should always be set to true.
                // NOTE: This request contains a single conversion as a demonstration.
                // However, if you have multiple conversions to upload, it's best to upload multiple
                // conversions per request instead of sending a separate request per conversion.
                // See the following for per-request limits:
                // https://developers.google.com/google-ads/api/docs/best-practices/quotas#conversion_upload
                UploadClickConversionsResponse response =
                    conversionUploadService.UploadClickConversions(
                        new UploadClickConversionsRequest()
                        {
                            CustomerId = customerId.ToString(),
                            Conversions = { clickConversion },
                            // Enables partial failure (must be true).
                            PartialFailure = true
                        });


                // Prints any partial errors returned.
                // To review the overall health of your recent uploads, see:
                // https://developers.google.com/google-ads/api/docs/conversions/upload-summaries
                if (response.PartialFailureError != null)
                {
                    // Extracts the partial failure from the response status.
                    GoogleAdsFailure partialFailure = response.PartialFailure;
                    Console.WriteLine($"{partialFailure.Errors.Count} partial failure error(s) " +
                        $"occurred");
                }
                else
                {
                    ClickConversionResult result = response.Results[0];
                    // Prints the result.
                    Console.WriteLine($"Uploaded conversion that occurred at" +
                        $" {result.ConversionDateTime} to {result.ConversionAction}.");
                }
            }
            catch (GoogleAdsException e)
            {
                Console.WriteLine("Failure:");
                Console.WriteLine($"Message: {e.Message}");
                Console.WriteLine($"Failure: {e.Failure}");
                Console.WriteLine($"Request ID: {e.RequestId}");
                throw;
            }
        }

        /// <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

<?php

/**
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace Google\Ads\GoogleAds\Examples\Remarketing;

require __DIR__ . '/../../vendor/autoload.php';

use GetOpt\GetOpt;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentNames;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentParser;
use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder;
use Google\Ads\GoogleAds\Lib\V16\GoogleAdsClient;
use Google\Ads\GoogleAds\Lib\V16\GoogleAdsClientBuilder;
use Google\Ads\GoogleAds\Lib\V16\GoogleAdsException;
use Google\Ads\GoogleAds\Util\V16\ResourceNames;
use Google\Ads\GoogleAds\V16\Common\Consent;
use Google\Ads\GoogleAds\V16\Common\OfflineUserAddressInfo;
use Google\Ads\GoogleAds\V16\Common\UserIdentifier;
use Google\Ads\GoogleAds\V16\Enums\ConsentStatusEnum\ConsentStatus;
use Google\Ads\GoogleAds\V16\Enums\UserIdentifierSourceEnum\UserIdentifierSource;
use Google\Ads\GoogleAds\V16\Errors\GoogleAdsError;
use Google\Ads\GoogleAds\V16\Services\ClickConversion;
use Google\Ads\GoogleAds\V16\Services\ClickConversionResult;
use Google\Ads\GoogleAds\V16\Services\UploadClickConversionsRequest;
use Google\ApiCore\ApiException;

/**
 * Uploads an enhanced conversion for leads by uploading a ClickConversion with hashed, first-party
 * user-provided data from your website lead forms. This includes user identifiers, and optionally a
 * click ID and order ID. With this information, Google can tie the conversion to the ad that drove
 * the lead.
 */
class UploadEnhancedConversionsForLeads
{
    private const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE';
    private const CONVERSION_ACTION_ID = 'INSERT_CONVERSION_ACTION_ID_HERE';
    // The date time at which the conversion 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'.
    private const CONVERSION_DATE_TIME = 'INSERT_CONVERSION_DATE_TIME_HERE';
    private const CONVERSION_VALUE = 'INSERT_CONVERSION_VALUE_HERE';

    // Optional: Specifies the order ID.
    private const ORDER_ID = null;
    // Optional: The Google Click ID for which conversions are uploaded.
    private const GCLID = null;
    // Optional: The consent status for ad user data.
    private const AD_USER_DATA_CONSENT = null;

    public static function main()
    {
        // Either pass the required parameters for this example on the command line, or insert them
        // into the constants above.
        $options = (new ArgumentParser())->parseCommandArguments([
            ArgumentNames::CUSTOMER_ID => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::CONVERSION_ACTION_ID => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::CONVERSION_DATE_TIME => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::CONVERSION_VALUE => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::ORDER_ID => GetOpt::OPTIONAL_ARGUMENT,
            ArgumentNames::GCLID => GetOpt::OPTIONAL_ARGUMENT,
            ArgumentNames::AD_USER_DATA_CONSENT => GetOpt::OPTIONAL_ARGUMENT
        ]);

        // Generate a refreshable OAuth2 credential for authentication.
        $oAuth2Credential = (new OAuth2TokenBuilder())->fromFile()->build();

        // Construct a Google Ads client configured from a properties file and the
        // OAuth2 credentials above.
        $googleAdsClient = (new GoogleAdsClientBuilder())
            ->fromFile()
            ->withOAuth2Credential($oAuth2Credential)
            // We set this value to true to show how to use GAPIC v2 source code. You can remove the
            // below line if you wish to use the old-style source code. Note that in that case, you
            // probably need to modify some parts of the code below to make it work.
            // For more information, see
            // https://developers.devsite.corp.google.com/google-ads/api/docs/client-libs/php/gapic.
            ->usingGapicV2Source(true)
            ->build();

        try {
            self::runExample(
                $googleAdsClient,
                $options[ArgumentNames::CUSTOMER_ID] ?: self::CUSTOMER_ID,
                $options[ArgumentNames::CONVERSION_ACTION_ID] ?: self::CONVERSION_ACTION_ID,
                $options[ArgumentNames::CONVERSION_DATE_TIME] ?: self::CONVERSION_DATE_TIME,
                $options[ArgumentNames::CONVERSION_VALUE] ?: self::CONVERSION_VALUE,
                $options[ArgumentNames::ORDER_ID] ?: self::ORDER_ID,
                $options[ArgumentNames::GCLID] ?: self::GCLID,
                $options[ArgumentNames::AD_USER_DATA_CONSENT]
                    ? ConsentStatus::value($options[ArgumentNames::AD_USER_DATA_CONSENT])
                    : self::AD_USER_DATA_CONSENT
            );
        } catch (GoogleAdsException $googleAdsException) {
            printf(
                "Request with ID '%s' has failed.%sGoogle Ads failure details:%s",
                $googleAdsException->getRequestId(),
                PHP_EOL,
                PHP_EOL
            );
            foreach ($googleAdsException->getGoogleAdsFailure()->getErrors() as $error) {
                /** @var GoogleAdsError $error */
                printf(
                    "\t%s: %s%s",
                    $error->getErrorCode()->getErrorCode(),
                    $error->getMessage(),
                    PHP_EOL
                );
            }
            exit(1);
        } catch (ApiException $apiException) {
            printf(
                "ApiException was thrown with message '%s'.%s",
                $apiException->getMessage(),
                PHP_EOL
            );
            exit(1);
        }
    }

    /**
     * Runs the example.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param int $conversionActionId the ID of the conversion action associated with this
     *      conversion
     * @param string $conversionDateTime the date and time of the conversion
     *      The format is "yyyy-mm-dd hh:mm:ss+|-hh:mm", e.g. “2019-01-01 12:32:45-08:00”
     * @param float $conversionValue the value of the conversion
     * @param string|null $orderId the unique order ID (transaction ID) of the conversion
     * @param string|null $gclid the Google click ID of the conversion
     * @param int|null $adUserDataConsent the ad user data consent for the click
     */
    public static function runExample(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        int $conversionActionId,
        string $conversionDateTime,
        float $conversionValue,
        ?string $orderId,
        ?string $gclid,
        ?int $adUserDataConsent
    ) {
        // Creates a click conversion with the specified attributes.
        $clickConversion = new ClickConversion();

        // Extract user email and phone from the raw data, normalize and hash it, then wrap it in
        // UserIdentifier objects. Creates a separate UserIdentifier object for each.
        // The data in this example is hardcoded, but in your application you might read the raw
        // data from an input file.

        // IMPORTANT: Since the identifier attribute of UserIdentifier
        // (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier) is a
        // oneof
        // (https://protobuf.dev/programming-guides/proto3/#oneof-features), you must set only ONE
        // of hashedEmail, hashedPhoneNumber, mobileId, thirdPartyUserId, or addressInfo. Setting
        // more than one of these attributes on the same UserIdentifier will clear all the other
        // members of the oneof. For example, the following code is INCORRECT and will result in a
        // UserIdentifier with ONLY a hashedPhoneNumber.
        //
        // $incorrectlyPopulatedUserIdentifier = new UserIdentifier([
        //    'hashed_email' => '...',
        //    'hashed_phone_number' => '...'
        // ]);

        $rawRecord = [
            // Email address that includes a period (.) before the Gmail domain.
            'email' => 'alex.2@example.com',
            // Phone number to be converted to E.164 format, with a leading '+' as required.
            'phone' => '+1 800 5550102',
            // This example lets you input conversion details as arguments, but in reality you might
            // store this data alongside other user data, so we include it in this sample user
            // record.
            'orderId' => $orderId,
            'gclid' => $gclid,
            'conversionActionId' => $conversionActionId,
            'conversionDateTime' => $conversionDateTime,
            'conversionValue' => $conversionValue,
            'currencyCode' => 'USD',
            'adUserDataConsent' => $adUserDataConsent
        ];

        // Creates a list for the user identifiers.
        $userIdentifiers = [];

        // 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";

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

        // Checks if the record has a phone number, and if so, adds a UserIdentifier for it.
        if (array_key_exists('phone', $rawRecord)) {
            $hashedPhoneNumberIdentifier = new UserIdentifier([
                'hashed_phone_number' => self::normalizeAndHash(
                    $hashAlgorithm,
                    $rawRecord['phone'],
                    true
                )
            ]);
            // Adds the hashed email identifier to the user identifiers list.
            $userIdentifiers[] = $hashedPhoneNumberIdentifier;
        }

        // Adds the user identifiers to the conversion.
        $clickConversion->setUserIdentifiers($userIdentifiers);

        // Adds details of the conversion.
        $clickConversion->setConversionAction(
            ResourceNames::forConversionAction($customerId, $rawRecord['conversionActionId'])
        );
        $clickConversion->setConversionDateTime($rawRecord['conversionDateTime']);
        $clickConversion->setConversionValue($rawRecord['conversionValue']);
        $clickConversion->setCurrencyCode($rawRecord['currencyCode']);

        // Sets the order ID if provided.
        if (!empty($rawRecord['orderId'])) {
            $clickConversion->setOrderId($rawRecord['orderId']);
        }

        // Sets the Google click ID (gclid) if provided.
        if (!empty($rawRecord['gclid'])) {
            $clickConversion->setGclid($rawRecord['gclid']);
        }

        // Sets the ad user data consent if provided.
        if (!empty($rawRecord['adUserDataConsent'])) {
            // Specifies whether user consent was obtained for the data you are uploading. See
            // https://www.google.com/about/company/user-consent-policy for details.
            $clickConversion->setConsent(
                new Consent(['ad_user_data' => $rawRecord['adUserDataConsent']])
            );
        }

        // Issues a request to upload the click conversion.
        $conversionUploadServiceClient = $googleAdsClient->getConversionUploadServiceClient();
        // NOTE: This request contains a single conversion as a demonstration.  However, if you have
        // multiple conversions to upload, it's best to upload multiple conversions per request
        // instead of sending a separate request per conversion. See the following for per-request
        // limits:
        // https://developers.google.com/google-ads/api/docs/best-practices/quotas#conversion_upload_service
        $response = $conversionUploadServiceClient->uploadClickConversions(
            // Enables partial failure (must be true).
            UploadClickConversionsRequest::build($customerId, [$clickConversion], true)
        );

        // Prints the status message if any partial failure error is returned.
        // Note: The details of each partial failure error are not printed here, you can refer to
        // the example HandlePartialFailure.php to learn more.
        // To review the overall health of your recent uploads, see:
        // https://developers.google.com/google-ads/api/docs/conversions/upload-summaries
        if ($response->hasPartialFailureError()) {
            printf(
                "Partial failures occurred: '%s'.%s",
                $response->getPartialFailureError()->getMessage(),
                PHP_EOL
            );
        } else {
            /** @var ClickConversionResult $clickConversionResult */
            $clickConversionResult = $response->getResults()[0];
            // Only prints valid results.
            if ($clickConversionResult->hasConversionDateTime()) {
                printf(
                    "Uploaded conversion that occurred at '%s' to '%s'.%s",
                    $clickConversionResult->getConversionDateTime(),
                    $clickConversionResult->getConversionAction(),
                    PHP_EOL
                );
            }
        }
    }

    /**
     * Returns the result of normalizing and then hashing the string using the provided hash
     * algorithm. Private customer data must be hashed during upload, as described at
     * https://support.google.com/google-ads/answer/7474263.
     *
     * @param string $hashAlgorithm the hash algorithm to use
     * @param string $value the value to normalize and hash
     * @return string the normalized and hashed value
     */
    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);
    }
}

UploadEnhancedConversionsForLeads::main();

      

Python

#!/usr/bin/env python
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Uploads an enhanced conversion for leads by uploading a ClickConversion.

The click conversion has hashed, first-party user-provided data from your
website lead forms. This includes user identifiers, and optionally, a click ID
and order ID. With this information, Google can tie the conversion to the ad
that drove the lead.
"""


import argparse
import hashlib
import re
import sys

from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException


def main(
    client,
    customer_id,
    conversion_action_id,
    conversion_date_time,
    conversion_value,
    order_id,
    gclid,
    ad_user_data_consent,
):
    """The main method that creates all necessary entities for the example.

    Args:
        client: An initialized GoogleAdsClient instance.
        customer_id: The client customer ID string.
        conversion_action_id: The ID of the conversion action to upload to.
        conversion_date_time: The date and time of the conversion.
        conversion_value: The value of the conversion.
        order_id: The unique ID (transaction ID) of the conversion.
        gclid: The Google click ID for the click.
        ad_user_data_consent: The consent status for ad user data for all
            members in the job.
    """
    # Extract user email and phone from the raw data, normalize and hash it,
    # then wrap it in UserIdentifier objects. Create a separate UserIdentifier
    # object for each. The data in this example is hardcoded, but in your
    # application you might read the raw data from an input file.

    # IMPORTANT: Since the identifier attribute of UserIdentifier
    # (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier)
    # is a oneof
    # (https://protobuf.dev/programming-guides/proto3/#oneof-features), you must
    # set only ONE of hashed_email, hashed_phone_number, mobile_id,
    # third_party_user_id, or address_info. Setting more than one of these
    # attributes on the same UserIdentifier will clear all the other members of
    # the oneof. For example, the following code is INCORRECT and will result in
    # a UserIdentifier with ONLY a hashed_phone_number:
    #
    # incorrectly_populated_user_identifier = client.get_type("UserIdentifier")
    # incorrectly_populated_user_identifier.hashed_email = "...""
    # incorrectly_populated_user_identifier.hashed_phone_number = "...""

    raw_record = {
        # Email address that includes a period (.) before the Gmail domain.
        "email": "alex.2@example.com",
        # Phone number to be converted to E.164 format, with a leading '+' as
        # required.
        "phone": "+1 800 5550102",
        # This example lets you input conversion details as arguments,
        # but in reality you might store this data alongside other user data,
        # so we include it in this sample user record.
        "order_id": order_id,
        "gclid": gclid,
        "conversion_action_id": conversion_action_id,
        "conversion_date_time": conversion_date_time,
        "conversion_value": conversion_value,
        "currency_code": "USD",
        "ad_user_data_consent": ad_user_data_consent,
    }

    # Constructs the click conversion.
    click_conversion = client.get_type("ClickConversion")
    # Creates a user identifier using the hashed email address, using the
    # normalize and hash method specifically for email addresses.
    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_phone_number = normalize_and_hash_email_address(
        raw_record["email"]
    )
    # Adds the user identifier to the conversion.
    click_conversion.user_identifiers.append(email_identifier)

    # Checks if the record has a phone number, and if so, adds a UserIdentifier
    # for it.
    if raw_record.get("phone") is not None:
        phone_identifier = client.get_type("UserIdentifier")
        phone_identifier.hashed_phone_number = normalize_and_hash(
            raw_record["phone"]
        )
        # Adds the phone identifier to the conversion adjustment.
        click_conversion.user_identifiers.append(phone_identifier)

    # Add details of the conversion.
    # Gets the conversion action resource name.
    conversion_action_service = client.get_service("ConversionActionService")
    click_conversion.conversion_action = (
        conversion_action_service.conversion_action_path(
            customer_id, raw_record["conversion_action_id"]
        )
    )
    click_conversion.conversion_date_time = raw_record["conversion_date_time"]
    click_conversion.conversion_value = raw_record["conversion_value"]
    click_conversion.currency_code = raw_record["currency_code"]

    # Sets the order ID if provided.
    if raw_record.get("order_id"):
        click_conversion.order_id = raw_record["order_id"]

    # Sets the gclid if provided.
    if raw_record.get("gclid"):
        click_conversion.gclid = raw_record["gclid"]

    # Specifies whether user consent was obtained for the data you are
    # uploading. For more details, see:
    # https://www.google.com/about/company/user-consent-policy
    if raw_record["ad_user_data_consent"]:
        click_conversion.consent.ad_user_data = client.enums.ConsentStatusEnum[
            raw_record["ad_user_data_consent"]
        ]

    # Creates the conversion upload service client.
    conversion_upload_service = client.get_service("ConversionUploadService")
    # Uploads the click conversion. Partial failure should always be set to
    # True.
    # NOTE: This request only uploads a single conversion, but if you have
    # multiple conversions to upload, it's most efficient to upload them in a
    # single request. See the following for per-request limits for reference:
    # https://developers.google.com/google-ads/api/docs/best-practices/quotas#conversion_upload_service
    response = conversion_upload_service.upload_click_conversions(
        customer_id=customer_id,
        conversions=[click_conversion],
        # Enables partial failure (must be true).
        partial_failure=True,
    )

    # Prints any partial errors returned.
    # To review the overall health of your recent uploads, see:
    # https://developers.google.com/google-ads/api/docs/conversions/upload-summaries
    if response.partial_failure_error:
        print(
            "Partial error encountered: "
            f"{response.partial_failure_error.message}"
        )
    else:
        # Prints the result.
        result = response.results[0]
        print(
            "Uploaded conversion that occurred at "
            f"{result.conversion_data_time} "
            f"to {result.conversion_action}."
        )


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="v16")

    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(
        "-d",
        "--conversion_date_time",
        type=str,
        required=True,
        help="The date time at which the conversion 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'.",
    )
    parser.add_argument(
        "-v",
        "--conversion_value",
        type=float,
        required=True,
        help="The value of the conversion.",
    )
    parser.add_argument(
        "-o",
        "--order_id",
        type=str,
        help="the unique ID (transaction ID) of the conversion.",
    )
    parser.add_argument(
        "-g",
        "--gclid",
        type=str,
        help="the Google click ID (gclid) for the click.",
    )
    parser.add_argument(
        "-d",
        "--ad_user_data_consent",
        type=str,
        choices=[e.name for e in googleads_client.enums.ConsentStatusEnum],
        help=(
            "The data consent status for ad user data for all members in "
            "the job."
        ),
    )
    args = parser.parse_args()

    try:
        main(
            googleads_client,
            args.customer_id,
            args.conversion_action_id,
            args.conversion_date_time,
            args.conversion_value,
            args.order_id,
            args.gclid,
            args.ad_user_data_consent,
        )
    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

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Uploads an enhanced conversion for leads by uploading a ClickConversion.
# The click conversion has hashed, first-party user-provided data from your
# website lead forms. This includes user identifiers, and optionally, a click ID
# and order ID. With this information, Google can tie the conversion to the ad
# that drove the lead.

require 'optparse'
require 'google/ads/google_ads'

def upload_conversion_with_identifiers(
  customer_id,
  conversion_action_id,
  conversion_date_time,
  conversion_value,
  order_id,
  gclid,
  ad_user_data_consent)
  # GoogleAdsClient will read a config file from
  # ENV['HOME']/google_ads_config.rb when called without parameters
  client = Google::Ads::GoogleAds::GoogleAdsClient.new

  # Extract user email and phone from the raw data, normalize and hash it,
  # then wrap it in UserIdentifier objects. Create a separate UserIdentifier
  # object for each. The data in this example is hardcoded, but in your
  # application you might read the raw data from an input file.

  # IMPORTANT: Since the identifier attribute of UserIdentifier
  # (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier)
  # is a oneof
  # (https://protobuf.dev/programming-guides/proto3/#oneof-features), you must
  # set only ONE of hashed_email, hashed_phone_number, mobile_id,
  # third_party_user_id, or address_info. Setting more than one of these
  # attributes on the same UserIdentifier will clear all the other members of
  # the oneof. For example, the following code is INCORRECT and will result in
  # a UserIdentifier with ONLY a hashed_phone_number:
  #
  # incorrectly_populated_user_identifier.hashed_email = "...""
  # incorrectly_populated_user_identifier.hashed_phone_number = "...""

  raw_record = {
    # Email address that includes a period (.) before the Gmail domain.
    "email" => "alex.2@example.com",
    # Phone number to be converted to E.164 format, with a leading '+' as
    # required.
    "phone" => "+1 800 5550102",
    # This example lets you input conversion details as arguments,
    # but in reality you might store this data alongside other user data,
    # so we include it in this sample user record.
    "order_id" => order_id,
    "gclid" => gclid,
    "conversion_action_id" => conversion_action_id,
    "conversion_date_time" => conversion_date_time,
    "conversion_value" => conversion_value,
    "currency_code" => "USD",
    "ad_user_data_consent" => ad_user_data_consent,
  }

  click_conversion = client.resource.click_conversion do |cc|
    cc.conversion_action = client.path.conversion_action(customer_id, conversion_action_id)
    cc.conversion_date_time = conversion_date_time
    cc.conversion_value = conversion_value.to_f
    cc.currency_code = 'USD'

    unless order_id.nil?
      cc.order_id = order_id
    end

    unless raw_record["gclid"].nil?
      cc.gclid = gclid
    end

    # Specifies whether user consent was obtained for the data you are
    # uploading. For more details, see:
    # https://www.google.com/about/company/user-consent-policy
    unless raw_record["ad_user_data_consent"].nil?
      cc.consent = client.resource.consent do |c|
        c.ad_user_data = ad_user_data_consent
      end
    end

    # Creates a user identifier using the hashed email address, using the
    # normalize and hash method specifically for email addresses.
    # If using a phone number, use the normalize_and_hash method instead.
    cc.user_identifiers << client.resource.user_identifier do |ui|
      ui.hashed_phone_number = normalize_and_hash_email(raw_record["email"])
      # Optional: Specifies the user identifier source.
      ui.user_identifier_source = :FIRST_PARTY
    end

    # Checks if the record has a phone number, and if so, adds a UserIdentifier
    # for it.
    unless raw_record["phone"].nil?
      cc.user_identifiers << client.resource.user_identifier do |ui|
        ui.hashed_phone_number = normalize_and_hash_email(raw_record["phone"])
      end
    end
  end

  response = client.service.conversion_upload.upload_click_conversions(
    customer_id: customer_id,
    conversions: [click_conversion],
    # Partial failure must be true.
    partial_failure: true,
  )

  if response.partial_failure_error
    puts "Partial failure encountered: #{response.partial_failure_error.message}"
  else
    result = response.results.first
    puts "Uploaded click conversion that happened at #{result.conversion_date_time} " \
      "to #{result.conversion_action}."
  end
end

# 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

if __FILE__ == $0
  options = {}
  # The following parameter(s) should be provided to run the example. You can
  # either specify these by changing the INSERT_XXX_ID_HERE values below, or on
  # the command line.
  #
  # Parameters passed on the command line will override any parameters set in
  # code.
  #
  # Running the example with -h will print the command line usage.
  options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE'
  options[:conversion_action_id] = 'INSERT_CONVERSION_ACTION_ID_HERE'
  options[:email_address] = 'INSERT_EMAIL_ADDRESS_HERE'
  options[:conversion_date_time] = 'INSERT_CONVERSION_DATE_TIME_HERE'
  options[:conversion_value] = 'INSERT_CONVERSION_VALUE_HERE'
  options[:order_id] = nil
  options[:gclid] = nil
  options[:ad_user_data_consent] = nil

  OptionParser.new do |opts|
    opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__))

    opts.separator ''
    opts.separator 'Options:'

    opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v|
      options[:customer_id] = v
    end

    opts.on('-c', '--conversion-action-id CONVERSION-ACTION-ID', String, 'Conversion Action ID') do |v|
      options[:conversion_action_id] = v
    end

    opts.on('-t', '--conversion-date-time CONVERSION-DATE-TIME', String,
            'The date and time of the conversion (should be after click time). ' \
            'The format is "yyyy-mm-dd hh:mm:ss+|-hh:mm", ' \
            'for example: “2019-01-01 12:32:45-08:00”') do |v|
      options[:conversion_date_time] = v
    end

    opts.on('-v', '--conversion-value CONVERSION-VALUE', String, 'Conversion Value') do |v|
      options[:conversion_value] = v
    end

    opts.on('-o', '--order-id ORDER-ID', String, 'Order ID (optional)') do |v|
      options[:order_id] = v
    end

    opts.on('-g', '--gclid GCLID', String, 'The Google click ID (gclid) for the click') do |v|
      options[:gclid] = v
    end

    opts.on('-d', '--ad-user-data-dconsent GCLID', String,
            'The data consent status for ad user data for all members in' \
            'the job.' \
            'e.g. UNKNOWN, GRANTED, DENIED') do |v|
      options[:ad_user_data_consent] = v
    end

    opts.separator ''
    opts.separator 'Help:'

    opts.on_tail('-h', '--help', 'Show this message') do
      puts opts
      exit
    end
  end.parse!

  begin
    upload_conversion_with_identifiers(
      options.fetch(:customer_id).tr("-", ""),
      options.fetch(:conversion_action_id),
      options.fetch(:conversion_date_time),
      options.fetch(:conversion_value),
      options[:order_id],
      options[:gclid],
      options[:ad_user_data_consent],
    )
  rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e
    e.failure.errors.each do |error|
      STDERR.printf("Error with message: %s\n", error.message)
      if error.location
        error.location.field_path_elements.each do |field_path_element|
          STDERR.printf("\tOn field: %s\n", field_path_element.field_name)
        end
      end
      error.error_code.to_h.each do |k, v|
        next if v == :UNSPECIFIED
        STDERR.printf("\tType: %s\n\tCode: %s\n", k, v)
      end
    end
  end
end

      

Perl

#!/usr/bin/perl -w
#
# Copyright 2021, Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Uploads an enhanced conversion for leads by uploading a ClickConversion
# with hashed, first-party user-provided data from your website lead forms.
# This includes user identifiers, and optionally a click ID and order ID.
# With this information, Google can tie the conversion to the ad that drove
# the lead.

use strict;
use warnings;
use utf8;

use FindBin qw($Bin);
use lib "$Bin/../../lib";
use Google::Ads::GoogleAds::Client;
use Google::Ads::GoogleAds::Utils::GoogleAdsHelper;
use Google::Ads::GoogleAds::V16::Common::Consent;
use Google::Ads::GoogleAds::V16::Common::UserIdentifier;
use Google::Ads::GoogleAds::V16::Enums::UserIdentifierSourceEnum
  qw(FIRST_PARTY);
use
  Google::Ads::GoogleAds::V16::Services::ConversionUploadService::ClickConversion;
use Google::Ads::GoogleAds::V16::Utils::ResourceNames;

use Getopt::Long qw(:config auto_help);
use Pod::Usage;
use Cwd         qw(abs_path);
use Digest::SHA qw(sha256_hex);

# The following parameter(s) should be provided to run the example. You can
# either specify these by changing the INSERT_XXX_ID_HERE values below, or on
# the command line.
#
# Parameters passed on the command line will override any parameters set in
# code.
#
# Running the example with -h will print the command line usage.
my $customer_id          = "INSERT_CUSTOMER_ID_HERE";
my $conversion_action_id = "INSERT_CONVERSION_ACTION_ID_HERE";
my $conversion_date_time = "INSERT_CONVERSION_DATE_TIME_HERE";
my $conversion_value     = "INSERT_CONVERSION_VALUE_HERE";
# Optional: Specify the unique order ID for the click conversion.
my $order_id = undef;
# Optional: Specify the Google click ID (gclid) for the click conversion.
my $gclid = undef;
# Optional: Specify the ad user data consent for the click.
my $ad_user_data_consent = undef;

sub upload_enhanced_conversions_for_leads {
  my ($api_client, $customer_id, $conversion_action_id, $conversion_date_time,
    $conversion_value, $order_id, $gclid, $ad_user_data_consent)
    = @_;

  # Create an empty click conversion.
  my $click_conversion =
    Google::Ads::GoogleAds::V16::Services::ConversionUploadService::ClickConversion
    ->new({});

  # Extract user email and phone from the raw data, normalize and hash it,
  # then wrap it in UserIdentifier objects. Create a separate UserIdentifier
  # object for each.
  # The data in this example is hardcoded, but in your application
  # you might read the raw data from an input file.
  #
  # IMPORTANT: Since the identifier attribute of UserIdentifier
  # (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier)
  # is a oneof
  # (https://protobuf.dev/programming-guides/proto3/#oneof-features), you must set
  # only ONE of hashed_email, hashed_phone_number, mobile_id, third_party_user_id,
  # or address-info. Setting more than one of these attributes on the same UserIdentifier
  # will clear all the other members of the oneof. For example, the following code is
  # INCORRECT and will result in a UserIdentifier with ONLY a hashed_phone_number:
  #
  # my $incorrect_user_identifier = Google::Ads::GoogleAds::V16::Common::UserIdentifier->new({
  #   hashedEmail => '...',
  #   hashedPhoneNumber => '...',
  # });
  my $raw_record = {
    # Email address that includes a period (.) before the Gmail domain.
    email => 'alex.2@example.com',
    # Phone number to be converted to E.164 format, with a leading '+' as
    # required.
    phone => '+1 800 5550102',
    # This example lets you input conversion details as arguments,
    # but in reality you might store this data alongside other user data,
    # so we include it in this sample user record.
    orderId            => $order_id,
    gclid              => $gclid,
    conversionActionId => $conversion_action_id,
    conversionDateTime => $conversion_date_time,
    conversionValue    => $conversion_value,
    currencyCode       => "USD",
    adUserDataConsent  => $ad_user_data_consent
  };
  my $user_identifiers = [];

  # Create a user identifier using the hashed email address, using the normalize
  # and hash method specifically for email addresses.
  my $hashed_email = normalize_and_hash_email_address($raw_record->{email});
  push(
    @$user_identifiers,
    Google::Ads::GoogleAds::V16::Common::UserIdentifier->new({
        hashedEmail => $hashed_email,
        # Optional: Specify the user identifier source.
        userIdentifierSource => FIRST_PARTY
      }));

  # Create a user identifier using normalized and hashed phone info.
  my $hashed_phone = normalize_and_hash($raw_record->{phone});
  push(
    @$user_identifiers,
    Google::Ads::GoogleAds::V16::Common::UserIdentifier->new({
        hashedPhone => $hashed_phone,
        # Optional: Specify the user identifier source.
        userIdentifierSource => FIRST_PARTY
      }));

  # Add the user identifiers to the conversion.
  $click_conversion->{userIdentifiers} = $user_identifiers;

  # Add details of the conversion.
  $click_conversion->{conversionAction} =
    Google::Ads::GoogleAds::V16::Utils::ResourceNames::conversion_action(
    $customer_id, $raw_record->{conversionActionId});
  $click_conversion->{conversionDateTime} = $raw_record->{conversionDateTime};
  $click_conversion->{conversionValue}    = $raw_record->{conversionValue};
  $click_conversion->{currencyCode}       = $raw_record->{currencyCode};

  # Set the order ID if provided.
  if (defined $raw_record->{orderId}) {
    $click_conversion->{orderId} = $raw_record->{orderId};
  }

  # Set the Google click ID (gclid) if provided.
  if (defined $raw_record->{gclid}) {
    $click_conversion->{gclid} = $raw_record->{gclid};
  }

  # Set the consent information, if provided.
  if (defined $raw_record->{adUserDataConsent}) {
    $click_conversion->{consent} =
      Google::Ads::GoogleAds::V16::Common::Consent->new({
        adUserData => $raw_record->{adUserDataConsent}});
  }

  # Upload the click conversion. Partial failure should always be set to true.
  #
  # NOTE: This request contains a single conversion as a demonstration.
  # However, if you have multiple conversions to upload, it's best to
  # upload multiple conversions per request instead of sending a separate
  # request per conversion. See the following for per-request limits:
  # https://developers.google.com/google-ads/api/docs/best-practices/quotas#conversion_upload_service
  my $response =
    $api_client->ConversionUploadService()->upload_click_conversions({
      customerId  => $customer_id,
      conversions => [$click_conversion],
      # Enable partial failure (must be true).
      partialFailure => "true"
    });

  # Print any partial errors returned.
  # To review the overall health of your recent uploads, see:
  # https://developers.google.com/google-ads/api/docs/conversions/upload-summaries
  if ($response->{partialFailureError}) {
    printf "Partial error encountered: '%s'.\n",
      $response->{partialFailureError}{message};
  }

  # Print the result.
  my $result = $response->{results}[0];
  # Only print valid results.
  if (defined $result->{conversionDateTime}) {
    printf "Uploaded conversion that occurred at '%s' to '%s'.\n",
      $result->{conversionDateTime},
      $result->{conversionAction};
  }

  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;

  # Removes leading, trailing, and intermediate spaces.
  $value =~ 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,
  "conversion_date_time=s" => \$conversion_date_time,
  "conversion_value=f"     => \$conversion_value,
  "order_id=s"             => \$order_id,
  "gclid=s"                => \$gclid,
  "ad_user_data_consent=s" => \$ad_user_data_consent,
);

# 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,
  $conversion_date_time, $conversion_value
  );

# Call the example.
upload_enhanced_conversions_for_leads($api_client, $customer_id =~ s/-//gr,
  $conversion_action_id, $conversion_date_time,
  $conversion_value,     $order_id, $gclid, $ad_user_data_consent);

=pod

=head1 NAME

upload_enhanced_conversions_for_leads

=head1 DESCRIPTION

Uploads an enhanced conversion for leads by uploading a ClickConversion
with hashed, first-party user-provided data from your website lead forms.

=head1 SYNOPSIS

upload_enhanced_conversions_for_leads.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.
    -conversion_date_time       The date time at which the conversion 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'.
    -conversion_value           The value of the conversion.
    -order_id                   [optional] The unique ID (transaction ID) of the conversion. We recommend including if available.
    -gclid                      [optional] The Google click ID associated with the conversion. We recommend including if available.
	-ad_user_data_consent		[optional] The ad user data consent for the click.

=cut