Add Customer Match User List

Java

// Copyright 2020 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 static com.google.ads.googleads.examples.utils.CodeSampleHelper.getPrintableDateTime;

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.v7.common.CrmBasedUserListInfo;
import com.google.ads.googleads.v7.common.CustomerMatchUserListMetadata;
import com.google.ads.googleads.v7.common.OfflineUserAddressInfo;
import com.google.ads.googleads.v7.common.UserData;
import com.google.ads.googleads.v7.common.UserIdentifier;
import com.google.ads.googleads.v7.enums.CustomerMatchUploadKeyTypeEnum.CustomerMatchUploadKeyType;
import com.google.ads.googleads.v7.enums.OfflineUserDataJobStatusEnum.OfflineUserDataJobStatus;
import com.google.ads.googleads.v7.enums.OfflineUserDataJobTypeEnum.OfflineUserDataJobType;
import com.google.ads.googleads.v7.errors.GoogleAdsError;
import com.google.ads.googleads.v7.errors.GoogleAdsException;
import com.google.ads.googleads.v7.resources.OfflineUserDataJob;
import com.google.ads.googleads.v7.resources.UserList;
import com.google.ads.googleads.v7.services.AddOfflineUserDataJobOperationsRequest;
import com.google.ads.googleads.v7.services.AddOfflineUserDataJobOperationsResponse;
import com.google.ads.googleads.v7.services.CreateOfflineUserDataJobResponse;
import com.google.ads.googleads.v7.services.GoogleAdsRow;
import com.google.ads.googleads.v7.services.GoogleAdsServiceClient;
import com.google.ads.googleads.v7.services.MutateUserListsResponse;
import com.google.ads.googleads.v7.services.OfflineUserDataJobOperation;
import com.google.ads.googleads.v7.services.OfflineUserDataJobServiceClient;
import com.google.ads.googleads.v7.services.SearchGoogleAdsStreamRequest;
import com.google.ads.googleads.v7.services.SearchGoogleAdsStreamResponse;
import com.google.ads.googleads.v7.services.UserListOperation;
import com.google.ads.googleads.v7.services.UserListServiceClient;
import com.google.api.gax.rpc.ServerStream;
import com.google.common.collect.ImmutableList;
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.Arrays;
import java.util.List;

/**
 * Creates a user list (a.k.a. audience) and uploads members to populate the list.
 *
 * <p><em>Notes:</em>
 *
 * <ul>
 *   <li>This feature is only available to allowlisted accounts. See
 *       https://support.google.com/adspolicy/answer/6299717 for more details.
 *   <li>It may take up to several hours for the list to be populated with members.
 *   <li>Email addresses must be associated with a Google account.
 *   <li>For privacy purposes, the user list size will show as zero until the list has at least
 *       1,000 members. After that, the size will be rounded to the two most significant digits.
 * </ul>
 */
public class AddCustomerMatchUserList {

  private static class AddCustomerMatchUserListParams extends CodeSampleParams {

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

  public static void main(String[] args) throws UnsupportedEncodingException {
    AddCustomerMatchUserListParams params = new AddCustomerMatchUserListParams();
    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");
    }

    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 AddCustomerMatchUserList().runExample(googleAdsClient, params.customerId);
    } 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.
   * @throws GoogleAdsException if an API request failed with one or more service errors.
   */
  private void runExample(GoogleAdsClient googleAdsClient, long customerId)
      throws UnsupportedEncodingException {
    // Creates a Customer Match user list.
    String userListResourceName = createCustomerMatchUserList(googleAdsClient, customerId);

    // Adds members to the user list.
    addUsersToCustomerMatchUserList(googleAdsClient, customerId, userListResourceName);
  }

  /**
   * Creates a Customer Match user list.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @return the resource name of the newly created user list.
   */
  private String createCustomerMatchUserList(GoogleAdsClient googleAdsClient, long customerId) {
    // Creates the new user list.
    UserList userList =
        UserList.newBuilder()
            .setName("Customer Match list #" + getPrintableDateTime())
            .setDescription("A list of customers that originated from email addresses")
            // Customer Match user lists can use a membership life span of 10,000 to indicate
            // unlimited; otherwise normal values apply.
            // Sets the membership life span to 30 days.
            .setMembershipLifeSpan(30)
            // Sets the upload key type to indicate the type of identifier that will be used to
            // add users to the list. This field is immutable and required for an ADD operation.
            .setCrmBasedUserList(
                CrmBasedUserListInfo.newBuilder()
                    .setUploadKeyType(CustomerMatchUploadKeyType.CONTACT_INFO))
            .build();

    // Creates the operation.
    UserListOperation operation = UserListOperation.newBuilder().setCreate(userList).build();

    // Creates the service client.
    try (UserListServiceClient userListServiceClient =
        googleAdsClient.getLatestVersion().createUserListServiceClient()) {
      // Adds the user list.
      MutateUserListsResponse response =
          userListServiceClient.mutateUserLists(
              Long.toString(customerId), ImmutableList.of(operation));
      // Prints the response.
      System.out.printf(
          "Created Customer Match user list with resource name: %s.%n",
          response.getResults(0).getResourceName());
      return response.getResults(0).getResourceName();
    }
  }

  /**
   * Creates and executes an asynchronous job to add users to the Customer Match user list.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param userListResourceName the resource name of the Customer Match user list to add members.
   *     to.
   */
  private void addUsersToCustomerMatchUserList(
      GoogleAdsClient googleAdsClient, long customerId, String userListResourceName)
      throws UnsupportedEncodingException {
    try (OfflineUserDataJobServiceClient offlineUserDataJobServiceClient =
        googleAdsClient.getLatestVersion().createOfflineUserDataJobServiceClient()) {
      // Creates a new offline user data job.
      OfflineUserDataJob offlineUserDataJob =
          OfflineUserDataJob.newBuilder()
              .setType(OfflineUserDataJobType.CUSTOMER_MATCH_USER_LIST)
              .setCustomerMatchUserListMetadata(
                  CustomerMatchUserListMetadata.newBuilder().setUserList(userListResourceName))
              .build();

      // Issues a request to create the offline user data job.
      CreateOfflineUserDataJobResponse createOfflineUserDataJobResponse =
          offlineUserDataJobServiceClient.createOfflineUserDataJob(
              Long.toString(customerId), offlineUserDataJob);
      String offlineUserDataJobResourceName = createOfflineUserDataJobResponse.getResourceName();
      System.out.printf(
          "Created an offline user data job with resource name: %s.%n",
          offlineUserDataJobResourceName);

      // Issues a request to add the operations to the offline user data job.
      List<OfflineUserDataJobOperation> userDataJobOperations = buildOfflineUserDataJobOperations();
      AddOfflineUserDataJobOperationsResponse response =
          offlineUserDataJobServiceClient.addOfflineUserDataJobOperations(
              AddOfflineUserDataJobOperationsRequest.newBuilder()
                  .setResourceName(offlineUserDataJobResourceName)
                  .setEnablePartialFailure(true)
                  .addAllOperations(userDataJobOperations)
                  .build());

      // 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.java to learn more.
      if (response.hasPartialFailureError()) {
        System.out.printf(
            "Encountered %d partial failure errors while adding %d operations to the offline user "
                + "data job: '%s'. Only the successfully added operations will be executed when "
                + "the job runs.%n",
            response.getPartialFailureError().getDetailsCount(),
            userDataJobOperations.size(),
            response.getPartialFailureError().getMessage());
      } else {
        System.out.printf(
            "Successfully added %d operations to the offline user data job.%n",
            userDataJobOperations.size());
      }

      // Issues an asynchronous request to run the offline user data job for executing
      // all added operations.
      offlineUserDataJobServiceClient.runOfflineUserDataJobAsync(offlineUserDataJobResourceName);

      // Offline user data jobs may take up to 24 hours to complete, so instead of waiting for the
      // job to complete, retrieves and displays the job status once. If the job is completed
      // successfully, prints information about the user list. Otherwise, prints the query to use
      // to check the job again later.
      checkJobStatus(
          googleAdsClient, customerId, offlineUserDataJobResourceName, userListResourceName);
    }
  }

  /**
   * Creates a list of offline user data job operations that will add users to the list.
   *
   * @return a list of operations.
   */
  private List<OfflineUserDataJobOperation> buildOfflineUserDataJobOperations()
      throws UnsupportedEncodingException {
    MessageDigest sha256Digest;
    try {
      sha256Digest = MessageDigest.getInstance("SHA-256");
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException("Missing SHA-256 algorithm implementation", e);
    }

    // Creates the first user data based on an email address.
    UserData userDataWithEmailAddress =
        UserData.newBuilder()
            .addUserIdentifiers(
                UserIdentifier.newBuilder()
                    .setHashedEmail(normalizeAndHash(sha256Digest, "customer@example.com")))
            .build();

    // Creates the second user data based on a physical address.
    UserData userDataWithPhysicalAddress =
        UserData.newBuilder()
            .addUserIdentifiers(
                UserIdentifier.newBuilder()
                    .setAddressInfo(
                        OfflineUserAddressInfo.newBuilder()
                            .setHashedFirstName(normalizeAndHash(sha256Digest, "John"))
                            .setHashedLastName(normalizeAndHash(sha256Digest, "Doe"))
                            .setCountryCode("US")
                            .setPostalCode("10011")))
            .build();

    // Creates the operations to add the two users.
    List<OfflineUserDataJobOperation> operations = new ArrayList<>();
    for (UserData userData : Arrays.asList(userDataWithEmailAddress, userDataWithPhysicalAddress)) {
      operations.add(OfflineUserDataJobOperation.newBuilder().setCreate(userData).build());
    }

    return operations;
  }

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

  /**
   * Retrieves, checks, and prints the status of the offline user data job.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param offlineUserDataJobResourceName the resource name of the OfflineUserDataJob to get the
   *     status for.
   * @param userListResourceName the resource name of the Customer Match user list.
   */
  private void checkJobStatus(
      GoogleAdsClient googleAdsClient,
      long customerId,
      String offlineUserDataJobResourceName,
      String userListResourceName) {
    try (GoogleAdsServiceClient googleAdsServiceClient =
        googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
      String query =
          String.format(
              "SELECT offline_user_data_job.resource_name, "
                  + "offline_user_data_job.id, "
                  + "offline_user_data_job.status, "
                  + "offline_user_data_job.type, "
                  + "offline_user_data_job.failure_reason "
                  + "FROM offline_user_data_job "
                  + "WHERE offline_user_data_job.resource_name = '%s'",
              offlineUserDataJobResourceName);
      // Issues the query and gets the GoogleAdsRow containing the job from the response.
      GoogleAdsRow googleAdsRow =
          googleAdsServiceClient
              .search(Long.toString(customerId), query)
              .iterateAll()
              .iterator()
              .next();
      OfflineUserDataJob offlineUserDataJob = googleAdsRow.getOfflineUserDataJob();
      System.out.printf(
          "Offline user data job ID %d with type '%s' has status: %s%n",
          offlineUserDataJob.getId(), offlineUserDataJob.getType(), offlineUserDataJob.getStatus());
      OfflineUserDataJobStatus jobStatus = offlineUserDataJob.getStatus();
      if (OfflineUserDataJobStatus.SUCCESS == jobStatus) {
        // Prints information about the user list.
        printCustomerMatchUserListInfo(googleAdsClient, customerId, userListResourceName);
      } else if (OfflineUserDataJobStatus.FAILED == jobStatus) {
        System.out.printf("  Failure reason: %s%n", offlineUserDataJob.getFailureReason());
      } else if (OfflineUserDataJobStatus.PENDING == jobStatus
          || OfflineUserDataJobStatus.RUNNING == jobStatus) {
        System.out.println();
        System.out.printf(
            "To check the status of the job periodically, use the following GAQL query with"
                + " GoogleAdsService.search:%n%s%n",
            query);
      }
    }
  }

  /**
   * Prints information about the Customer Match user list.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID .
   * @param userListResourceName the resource name of the Customer Match user list.
   */
  private void printCustomerMatchUserListInfo(
      GoogleAdsClient googleAdsClient, long customerId, String userListResourceName) {
    try (GoogleAdsServiceClient googleAdsServiceClient =
        googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
      // Creates a query that retrieves the user list.
      String query =
          String.format(
              "SELECT user_list.size_for_display, user_list.size_for_search "
                  + "FROM user_list "
                  + "WHERE user_list.resource_name = '%s'",
              userListResourceName);

      // Constructs the SearchGoogleAdsStreamRequest.
      SearchGoogleAdsStreamRequest request =
          SearchGoogleAdsStreamRequest.newBuilder()
              .setCustomerId(Long.toString(customerId))
              .setQuery(query)
              .build();

      // Issues the search stream request.
      ServerStream<SearchGoogleAdsStreamResponse> stream =
          googleAdsServiceClient.searchStreamCallable().call(request);

      // Gets the first and only row from the response.
      GoogleAdsRow googleAdsRow = stream.iterator().next().getResultsList().get(0);
      UserList userList = googleAdsRow.getUserList();
      System.out.printf(
          "User list '%s' has an estimated %d users for Display and %d users for Search.%n",
          userList.getResourceName(), userList.getSizeForDisplay(), userList.getSizeForSearch());
      System.out.println(
          "Reminder: It may take several hours for the user list to be populated with the users.");
    }
  }
}

      

C#

// Copyright 2020 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 Google.Ads.GoogleAds.Lib;
using Google.Ads.GoogleAds.V7.Common;
using Google.Ads.GoogleAds.V7.Errors;
using Google.Ads.GoogleAds.V7.Resources;
using Google.Ads.GoogleAds.V7.Services;
using Google.Api.Gax;
using Google.LongRunning;
using Google.Protobuf.WellKnownTypes;
using System;
using System.Security.Cryptography;
using System.Text;
using static Google.Ads.GoogleAds.V7.Enums.CustomerMatchUploadKeyTypeEnum.Types;
using static Google.Ads.GoogleAds.V7.Enums.OfflineUserDataJobTypeEnum.Types;

namespace Google.Ads.GoogleAds.Examples.V7
{
    /// <summary>
    ///  This code example uses Customer Match to create a new user list (a.k.a. audience) and adds
    ///  users to it.
    ///
    ///  Note: It may take up to several hours for the list to be populated with users.
    ///  Email addresses must be associated with a Google account.
    ///  For privacy purposes, the user list size will show as zero until the list has
    ///  at least 1,000 users. After that, the size will be rounded to the two most
    ///  significant digits.
    /// </summary>
    public class AddCustomerMatchUserList : ExampleBase
    {
        private const int POLL_FREQUENCY_SECONDS = 1;
        private const int MAX_TOTAL_POLL_INTERVAL_SECONDS = 60;

        private static SHA256 digest = SHA256.Create();

        /// <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)
        {
            AddCustomerMatchUserList codeExample = new AddCustomerMatchUserList();
            Console.WriteLine(codeExample.Description);

            // The Google Ads customer ID for which the call is made.
            long customerId = long.Parse("INSERT_CUSTOMER_ID_HERE");

            codeExample.Run(new GoogleAdsClient(), customerId);
        }

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description =>
            "This code example uses Customer Match to create a new user list (a.k.a. audience) " +
            "and adds users to it. \nNote: It may take up to several hours for the list to be " +
            "populated with users. Email addresses must be associated with a Google account. For " +
            "privacy purposes, the user list size will show as zero until the list has at least " +
            "1,000 users. After that, the size will be rounded to the two most significant digits.";

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The Google Ads customer ID for which the user list is added.
        /// </param>
        public void Run(GoogleAdsClient client, long customerId)
        {
            try
            {
                string userListResourceName = CreateCustomerMatchUserList(client, customerId);
                AddUsersToCustomerMatchUserList(client, customerId, userListResourceName);
                PrintCustomerMatchUserListInfo(client, customerId, userListResourceName);
            }
            catch (GoogleAdsException e)
            {
                Console.WriteLine("Failure:");
                Console.WriteLine($"Message: {e.Message}");
                Console.WriteLine($"Failure: {e.Failure}");
                Console.WriteLine($"Request ID: {e.RequestId}");
                throw;
            }
        }

        /// <summary>
        /// Creates the customer match user list.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The Google Ads customer ID for which the user list is added.
        /// </param>
        /// <returns>The resource name of the newly created user list</returns>
        private string CreateCustomerMatchUserList(GoogleAdsClient client, long customerId)
        {
            // Get the UserListService.
            UserListServiceClient service = client.GetService(Services.V7.UserListService);

            // Creates the user list.
            UserList userList = new UserList()
            {
                Name = $"Customer Match list# {ExampleUtilities.GetShortRandomString()}",
                Description = "A list of customers that originated from email and physical" +
                    " addresses",
                // Customer Match user lists can use a membership life span of 10000 to
                // indicate unlimited; otherwise normal values apply.
                // Sets the membership life span to 30 days.
                MembershipLifeSpan = 30,
                CrmBasedUserList = new CrmBasedUserListInfo()
                {
                    UploadKeyType = CustomerMatchUploadKeyType.ContactInfo
                }
            };
            // Creates the user list operation.
            UserListOperation operation = new UserListOperation()
            {
                Create = userList
            };

            // Issues a mutate request to add the user list and prints some information.
            MutateUserListsResponse response = service.MutateUserLists(
                customerId.ToString(), new[] { operation });
            string userListResourceName = response.Results[0].ResourceName;
            Console.WriteLine($"User list with resource name '{userListResourceName}' " +
                $"was created.");
            return userListResourceName;
        }

        /// <summary>
        /// Creates and executes an asynchronous job to add users to the Customer Match user list.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The Google Ads customer ID for which calls are made.
        /// </param>
        /// <param name="userListResourceName">the resource name of the Customer Match user list
        /// to add users to</param>
        private static void AddUsersToCustomerMatchUserList(GoogleAdsClient client,
            long customerId, string userListResourceName)
        {
            // Get the OfflineUserDataJobService.
            OfflineUserDataJobServiceClient service = client.GetService(
                Services.V7.OfflineUserDataJobService);

            // Creates a new offline user data job.
            OfflineUserDataJob offlineUserDataJob = new OfflineUserDataJob()
            {
                Type = OfflineUserDataJobType.CustomerMatchUserList,
                CustomerMatchUserListMetadata = new CustomerMatchUserListMetadata()
                {
                    UserList = userListResourceName
                }
            };

            // Issues a request to create the offline user data job.
            CreateOfflineUserDataJobResponse response1 = service.CreateOfflineUserDataJob(
                customerId.ToString(), offlineUserDataJob);
            string offlineUserDataJobResourceName = response1.ResourceName;
            Console.WriteLine($"Created an offline user data job with resource name: " +
                $"'{offlineUserDataJobResourceName}'.");

            AddOfflineUserDataJobOperationsRequest request =
                new AddOfflineUserDataJobOperationsRequest()
                {
                    ResourceName = offlineUserDataJobResourceName,
                    Operations = { BuildOfflineUserDataJobOperations() },
                    EnablePartialFailure = true,
                };
            // Issues a request to add the operations to the offline user data job.
            AddOfflineUserDataJobOperationsResponse response2 =
                service.AddOfflineUserDataJobOperations(request);

            // 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.cs to learn more.
            if (response2.PartialFailureError != null)
            {
                // Extracts the partial failure from the response status.
                GoogleAdsFailure partialFailure = response2.PartialFailure;
                Console.WriteLine($"{partialFailure.Errors.Count} partial failure error(s) " +
                    $"occurred");
            }
            Console.WriteLine("The operations are added to the offline user data job.");

            // Issues an asynchronous request to run the offline user data job for executing
            // all added operations.
            Operation<Empty, Empty> operationResponse =
                service.RunOfflineUserDataJob(offlineUserDataJobResourceName);

            Console.WriteLine("Asynchronous request to execute the added operations started."); ;
            Console.WriteLine("Waiting until operation completes.");

            // PollUntilCompleted() implements a default back-off policy for retrying. You can
            // tweak the polling behaviour using a PollSettings as illustrated below.
            operationResponse.PollUntilCompleted(new PollSettings(
                Expiration.FromTimeout(TimeSpan.FromSeconds(MAX_TOTAL_POLL_INTERVAL_SECONDS)),
                TimeSpan.FromSeconds(POLL_FREQUENCY_SECONDS)));

            if (operationResponse.IsCompleted)
            {
                Console.WriteLine($"Offline user data job with resource name " +
                    $"'{offlineUserDataJobResourceName}' has finished.");
            }
            else
            {
                Console.WriteLine($"Offline user data job with resource name" +
                    $" '{offlineUserDataJobResourceName}' is pending after " +
                    $"{MAX_TOTAL_POLL_INTERVAL_SECONDS} seconds.");
            }
        }

        /// <summary>
        /// Builds and returns offline user data job operations to add one user identified by an
        /// email address and one user identified based on a physical address.
        /// </summary>
        /// <returns>An array with the operations</returns>
        private static OfflineUserDataJobOperation[] BuildOfflineUserDataJobOperations()
        {
            // Creates a first user data based on an email address.
            UserData userDataWithEmailAddress = new UserData()
            {
                UserIdentifiers = {
                    new UserIdentifier()
                    {
                        // Hash normalized email addresses based on SHA-256 hashing algorithm.
                        HashedEmail = NormalizeAndHash("customer@example.com")
                    }
                }
            };

            // Creates a second user data based on a physical address.
            UserData userDataWithPhysicalAddress = new UserData()
            {
                UserIdentifiers =
                {
                    new UserIdentifier()
                    {
                        AddressInfo = new OfflineUserAddressInfo()
                        {
                            // First and last name must be normalized and hashed.
                            HashedFirstName = NormalizeAndHash("John"),
                            HashedLastName = NormalizeAndHash("Doe"),
                            // Country code and zip code are sent in plain text.
                            CountryCode = "US",
                            PostalCode = "10011"
                        }
                    }
                }
            };

            // Creates the operations to add the two users.
            return new OfflineUserDataJobOperation[]
            {
                new OfflineUserDataJobOperation()
                {
                    Create = userDataWithEmailAddress
                },
                new OfflineUserDataJobOperation()
                {
                    Create = userDataWithPhysicalAddress
                }
            };
        }

        /// <summary>
        /// Prints information about the Customer Match user list.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The Google Ads customer ID for which calls are made.
        /// </param>
        /// <param name="userListResourceName">The resource name of the Customer Match user list
        /// to print information about.</param>
        private void PrintCustomerMatchUserListInfo(GoogleAdsClient client, long customerId,
            string userListResourceName)
        {
            // Get the GoogleAdsService.
            GoogleAdsServiceClient service =
                client.GetService(Services.V7.GoogleAdsService);

            // Creates a query that retrieves the user list.
            string query =
                "SELECT user_list.size_for_display, user_list.size_for_search " +
                "FROM user_list " +
                $"WHERE user_list.resource_name = '{userListResourceName}'";
            // Issues a search stream request.
            service.SearchStream(customerId.ToString(), query,
               delegate (SearchGoogleAdsStreamResponse resp)
               {
                   // Display the results.
                   foreach (GoogleAdsRow userListRow in resp.Results)
                   {
                       UserList userList = userListRow.UserList;
                       Console.WriteLine("The estimated number of users that the user list " +
                           $"'{userList.ResourceName}' has is {userList.SizeForDisplay}" +
                           $" for Display and {userList.SizeForSearch} for Search.");
                   }
               }
           );

            Console.WriteLine("Reminder: It may take several hours for the user list to be " +
                "populated with the users so getting zeros for the estimations is expected.");
        }

        /// <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 2020 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\Examples\Utils\Helper;
use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder;
use Google\Ads\GoogleAds\Lib\V7\GoogleAdsClient;
use Google\Ads\GoogleAds\Lib\V7\GoogleAdsClientBuilder;
use Google\Ads\GoogleAds\Lib\V7\GoogleAdsException;
use Google\Ads\GoogleAds\Lib\V7\GoogleAdsServerStreamDecorator;
use Google\Ads\GoogleAds\Util\V7\GoogleAdsFailures;
use Google\Ads\GoogleAds\V7\Common\CrmBasedUserListInfo;
use Google\Ads\GoogleAds\V7\Common\CustomerMatchUserListMetadata;
use Google\Ads\GoogleAds\V7\Common\OfflineUserAddressInfo;
use Google\Ads\GoogleAds\V7\Common\UserData;
use Google\Ads\GoogleAds\V7\Common\UserIdentifier;
use Google\Ads\GoogleAds\V7\Enums\CustomerMatchUploadKeyTypeEnum\CustomerMatchUploadKeyType;
use Google\Ads\GoogleAds\V7\Enums\OfflineUserDataJobTypeEnum\OfflineUserDataJobType;
use Google\Ads\GoogleAds\V7\Errors\GoogleAdsError;
use Google\Ads\GoogleAds\V7\Resources\OfflineUserDataJob;
use Google\Ads\GoogleAds\V7\Resources\UserList;
use Google\Ads\GoogleAds\V7\Services\AddOfflineUserDataJobOperationsResponse;
use Google\Ads\GoogleAds\V7\Services\CreateOfflineUserDataJobResponse;
use Google\Ads\GoogleAds\V7\Services\GoogleAdsRow;
use Google\Ads\GoogleAds\V7\Services\OfflineUserDataJobOperation;
use Google\Ads\GoogleAds\V7\Services\UserListOperation;
use Google\ApiCore\ApiException;
use Google\ApiCore\OperationResponse;

/**
 * This example uses Customer Match to create a new user list (a.k.a. audience) and adds users to
 * it.
 *
 * Note: It may take up to several hours for the list to be populated with users.
 * Email addresses must be associated with a Google account.
 * For privacy purposes, the user list size will show as zero until the list has
 * at least 1,000 users. After that, the size will be rounded to the two most
 * significant digits.
 */
class AddCustomerMatchUserList
{
    private const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE';

    private const POLL_FREQUENCY_SECONDS = 1;
    private const MAX_TOTAL_POLL_INTERVAL_SECONDS = 60;

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

        // 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)
            ->build();

        try {
            self::runExample(
                $googleAdsClient,
                $options[ArgumentNames::CUSTOMER_ID] ?: self::CUSTOMER_ID
            );
        } 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
     */
    public static function runExample(GoogleAdsClient $googleAdsClient, int $customerId)
    {
        $userListResourceName = self::createCustomerMatchUserList($googleAdsClient, $customerId);
        self::addUsersToCustomerMatchUserList($googleAdsClient, $customerId, $userListResourceName);
        self::printCustomerMatchUserListInfo($googleAdsClient, $customerId, $userListResourceName);
    }

    /**
     * Creates a Customer Match user list.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @return string the resource name of the newly created user list
     */
    private static function createCustomerMatchUserList(
        GoogleAdsClient $googleAdsClient,
        int $customerId
    ): string {
        // Creates the user list.
        $userList = new UserList([
            'name' => 'Customer Match list #' . Helper::getPrintableDatetime(),
            'description' => 'A list of customers that originated from email '
                . 'and physical addresses',
            // Customer Match user lists can use a membership life span of 10000 to
            // indicate unlimited; otherwise normal values apply.
            // Sets the membership life span to 30 days.
            'membership_life_span' => 30,
            'crm_based_user_list' => new CrmBasedUserListInfo([
                'upload_key_type' => CustomerMatchUploadKeyType::CONTACT_INFO
            ])
        ]);

        // Creates the user list operation.
        $operation = new UserListOperation();
        $operation->setCreate($userList);

        // Issues a mutate request to add the user list and prints some information.
        $userListServiceClient = $googleAdsClient->getUserListServiceClient();
        $response = $userListServiceClient->mutateUserLists($customerId, [$operation]);
        $userListResourceName = $response->getResults()[0]->getResourceName();
        printf("User list with resource name '%s' was created.%s", $userListResourceName, PHP_EOL);

        return $userListResourceName;
    }

    /**
     * Creates and executes an asynchronous job to add users to the Customer Match user list.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param string $userListResourceName the resource name of the Customer Match user list to add
     *     users to
     */
    private static function addUsersToCustomerMatchUserList(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $userListResourceName
    ) {
        $offlineUserDataJobServiceClient = $googleAdsClient->getOfflineUserDataJobServiceClient();

        // Creates a new offline user data job.
        $offlineUserDataJob = new OfflineUserDataJob([
            'type' => OfflineUserDataJobType::CUSTOMER_MATCH_USER_LIST,
            'customer_match_user_list_metadata' => new CustomerMatchUserListMetadata([
                'user_list' => $userListResourceName
            ])
        ]);

        // Issues a request to create the offline user data job.
        /** @var CreateOfflineUserDataJobResponse $createOfflineUserDataJobResponse */
        $createOfflineUserDataJobResponse =
            $offlineUserDataJobServiceClient->createOfflineUserDataJob(
                $customerId,
                $offlineUserDataJob
            );
        $offlineUserDataJobResourceName = $createOfflineUserDataJobResponse->getResourceName();
        printf(
            "Created an offline user data job with resource name: '%s'.%s",
            $offlineUserDataJobResourceName,
            PHP_EOL
        );

        // Issues a request to add the operations to the offline user data job.
        /** @var AddOfflineUserDataJobOperationsResponse $operationResponse */
        $response = $offlineUserDataJobServiceClient->addOfflineUserDataJobOperations(
            $offlineUserDataJobResourceName,
            self::buildOfflineUserDataJobOperations(),
            ['enablePartialFailure' => 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.
        if (!is_null($response->getPartialFailureError())) {
            // Extracts the partial failure from the response status.
            $partialFailure = GoogleAdsFailures::fromAny(
                $response->getPartialFailureError()->getDetails()->getIterator()->current()
            );
            printf(
                "%d partial failure error(s) occurred: %s.%s",
                count($partialFailure->getErrors()),
                $response->getPartialFailureError()->getMessage(),
                PHP_EOL
            );
        }
        print 'The operations are added to the offline user data job.' . PHP_EOL;

        // Issues an asynchronous request to run the offline user data job for executing all added
        // operations.
        /** @var OperationResponse $operationResponse */
        $operationResponse = $offlineUserDataJobServiceClient->runOfflineUserDataJob(
            $offlineUserDataJobResourceName
        );
        print 'Asynchronous request to execute the added operations started.' . PHP_EOL;
        print 'Waiting until operation completes.' . PHP_EOL;

        // pollUntilComplete() implements a default back-off policy for retrying. You can tweak the
        // retrying parameters like the maximum polling interval to use by passing them as an array
        // to the pollUntilComplete() function. Visit the OperationResponse.php file for more
        // details.
        $operationCompleted = $operationResponse->pollUntilComplete([
            'initialPollDelayMillis' => self::POLL_FREQUENCY_SECONDS * 1000,
            'totalPollTimeoutMillis' => self::MAX_TOTAL_POLL_INTERVAL_SECONDS * 1000
        ]);
        if ($operationCompleted) {
            printf(
                "Offline user data job with resource name '%s' has finished.%s",
                $offlineUserDataJobResourceName,
                PHP_EOL
            );
        } else {
            printf(
                "Offline user data job with resource name '%s' still pending after %d " .
                "seconds, continuing the execution of the code example anyway.%s",
                $offlineUserDataJobResourceName,
                self::MAX_TOTAL_POLL_INTERVAL_SECONDS,
                PHP_EOL
            );
        }
    }

    /**
     * Builds and returns offline user data job operations to add one user identified by an
     * email address and one user identified based on a physical address.
     *
     * @return OfflineUserDataJobOperation[] an array with the operations
     */
    private static function buildOfflineUserDataJobOperations(): array
    {
        // Creates a first user data based on an email address.
        $userDataWithEmailAddress = new UserData([
            'user_identifiers' => [
                new UserIdentifier([
                    // Hash normalized email addresses based on SHA-256 hashing algorithm.
                    'hashed_email' => self::normalizeAndHash('customer@example.com')
                ])
            ]
        ]);

        // Creates a second user data based on a physical address.
        $userDataWithPhysicalAddress = new UserData([
            'user_identifiers' => [
                new UserIdentifier([
                    'address_info' => new OfflineUserAddressInfo([
                        // First and last name must be normalized and hashed.
                        'hashed_first_name' => self::normalizeAndHash('John'),
                        'hashed_last_name' => self::normalizeAndHash('Doe'),
                        // Country code and zip code are sent in plain text.
                        'country_code' => 'US',
                        'postal_code' => '10011'
                    ])
                ])
            ]
        ]);

        // Creates the operations to add the two users.
        $operations = [
            new OfflineUserDataJobOperation(['create' => $userDataWithEmailAddress]),
            new OfflineUserDataJobOperation(['create' => $userDataWithPhysicalAddress])
        ];

        return $operations;
    }

    /**
     * Prints information about the Customer Match user list.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param string $userListResourceName the resource name of the Customer Match user list to
     *     print information about
     */
    private static function printCustomerMatchUserListInfo(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $userListResourceName
    ) {
        $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();

        // Creates a query that retrieves the user list.
        $query =
            "SELECT user_list.size_for_display, user_list.size_for_search " .
            "FROM user_list " .
            "WHERE user_list.resource_name = '$userListResourceName'";

        // Issues a search stream request.
        /** @var GoogleAdsServerStreamDecorator $stream */
        $stream = $googleAdsServiceClient->searchStream($customerId, $query);

        // Prints out some information about the user list.
        /** @var GoogleAdsRow $googleAdsRow */
        $googleAdsRow = $stream->iterateAllElements()->current();
        printf(
            "The estimated number of users that the user list '%s' has is %d for Display " .
             "and %d for Search.%s",
            $googleAdsRow->getUserList()->getResourceName(),
            $googleAdsRow->getUserList()->getSizeForDisplay(),
            $googleAdsRow->getUserList()->getSizeForSearch(),
            PHP_EOL
        );
        print 'Reminder: It may take several hours for the user list to be populated with the ' .
            'users so getting zeros for the estimations is expected.' . PHP_EOL;
    }

    /**
     * Normalizes and hashes a string value.
     *
     * @param string $value the value to normalize and hash
     * @return string the normalized and hashed value
     */
    private static function normalizeAndHash(string $value): string
    {
        return hash('sha256', strtolower(trim($value)));
    }
}

AddCustomerMatchUserList::main();

      

Python

#!/usr/bin/env python
# Copyright 2020 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.
"""Uses Customer Match to create and add users to a new user (audience) list.

Note: It may take up to several hours for the list to be populated with users.
Email addresses must be associated with a Google account.
For privacy purposes, the user list size will show as zero until the list has
at least 1,000 users. After that, the size will be rounded to the two most
significant digits.
"""

import argparse
import hashlib
import sys
import uuid

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


def main(client, customer_id, skip_polling):
    """Uses Customer Match to create and add users to a new user list.

    Args:
        client: The Google Ads client.
        customer_id: The customer ID for which to add the user list.
        skip_polling: A bool dictating whether to poll the API for completion.
    """
    user_list_resource_name = _create_customer_match_user_list(
        client, customer_id
    )
    _add_users_to_customer_match_user_list(
        client, customer_id, user_list_resource_name, skip_polling
    )


def _create_customer_match_user_list(client, customer_id):
    """Creates a Customer Match user list.

    Args:
        client: The Google Ads client.
        customer_id: The customer ID for which to add the user list.

    Returns:
        The string resource name of the newly created user list.
    """
    # Creates the UserListService client.
    user_list_service_client = client.get_service("UserListService")

    # Creates the user list operation.
    user_list_operation = client.get_type("UserListOperation")

    # Creates the new user list.
    user_list = user_list_operation.create
    user_list.name = f"Customer Match list #{uuid.uuid4()}"
    user_list.description = (
        "A list of customers that originated from email and physical addresses"
    )
    user_list.crm_based_user_list.upload_key_type = client.get_type(
        "CustomerMatchUploadKeyTypeEnum"
    ).CustomerMatchUploadKeyType.CONTACT_INFO
    # Customer Match user lists can set an unlimited membership life span;
    # to do so, use the special life span value 10000. Otherwise, membership
    # life span must be between 0 and 540 days inclusive. See:
    # https://developers.devsite.corp.google.com/google-ads/api/reference/rpc/latest/UserList#membership_life_span
    # Sets the membership life span to 30 days.
    user_list.membership_life_span = 30

    response = user_list_service_client.mutate_user_lists(
        customer_id=customer_id, operations=[user_list_operation]
    )
    user_list_resource_name = response.results[0].resource_name
    print(
        f"User list with resource name '{user_list_resource_name}' was created."
    )

    return user_list_resource_name


def _add_users_to_customer_match_user_list(
    client, customer_id, user_list_resource_name, skip_polling
):
    """Uses Customer Match to create and add users to a new user list.

    Args:
        client: The Google Ads client.
        customer_id: The customer ID for which to add the user list.
        user_list_resource_name: The resource name of the user list to which to
            add users.
        skip_polling: A bool dictating whether to poll the API for completion.
    """
    # Creates the OfflineUserDataJobService client.
    offline_user_data_job_service_client = client.get_service(
        "OfflineUserDataJobService"
    )

    # Creates a new offline user data job.
    offline_user_data_job = client.get_type("OfflineUserDataJob")
    offline_user_data_job.type_ = client.get_type(
        "OfflineUserDataJobTypeEnum"
    ).OfflineUserDataJobType.CUSTOMER_MATCH_USER_LIST
    offline_user_data_job.customer_match_user_list_metadata.user_list = (
        user_list_resource_name
    )

    # Issues a request to create an offline user data job.
    create_offline_user_data_job_response = (
        offline_user_data_job_service_client.create_offline_user_data_job(
            customer_id=customer_id, job=offline_user_data_job
        )
    )
    offline_user_data_job_resource_name = (
        create_offline_user_data_job_response.resource_name
    )
    print(
        "Created an offline user data job with resource name: "
        f"'{offline_user_data_job_resource_name}'."
    )

    request = client.get_type("AddOfflineUserDataJobOperationsRequest")
    request.resource_name = offline_user_data_job_resource_name
    request.operations = _build_offline_user_data_job_operations(client)
    request.enable_partial_failure = True

    # Issues a request to add the operations to the offline user data job.
    response = offline_user_data_job_service_client.add_offline_user_data_job_operations(
        request=request
    )

    # Prints the status message if any partial failure error is returned.
    # Note: the details of each partial failure error are not printed here.
    # Refer to the error_handling/handle_partial_failure.py example to learn
    # more.
    # Extracts the partial failure from the response status.
    partial_failure = getattr(response, "partial_failure_error", None)
    if getattr(partial_failure, "code", None) != 0:
        error_details = getattr(partial_failure, "details", [])
        for error_detail in error_details:
            failure_message = client.get_type("GoogleAdsFailure")
            # Retrieve the class definition of the GoogleAdsFailure instance
            # in order to use the "deserialize" class method to parse the
            # error_detail string into a protobuf message object.
            failure_object = type(failure_message).deserialize(error_detail)

            for error in failure_object.errors:
                print(
                    "A partial failure at index "
                    f"{error.location.field_path_elements[0].index} occurred.\n"
                    f"Error message: {error.message}\n"
                    f"Error code: {error.error_code}"
                )

    print("The operations are added to the offline user data job.")

    # Issues an request to run the offline user data job for executing all
    # added operations.
    operation_response = (
        offline_user_data_job_service_client.run_offline_user_data_job(
            resource_name=offline_user_data_job_resource_name
        )
    )

    if skip_polling:
        _check_job_status(
            client,
            customer_id,
            offline_user_data_job_resource_name,
            user_list_resource_name,
        )
    else:
        # Wait until the operation has finished.
        print("Request to execute the added operations started.")
        print("Waiting until operation completes...")
        operation_response.result()
        _print_customer_match_user_list_info(
            client, customer_id, user_list_resource_name
        )


def _build_offline_user_data_job_operations(client):
    """Builds and returns two sample offline user data job operations.

    Args:
        client: The Google Ads client.

    Returns:
        A list containing the operations.
    """
    # Creates a first user data based on an email address.
    user_data_with_email_address_operation = client.get_type(
        "OfflineUserDataJobOperation"
    )
    user_data_with_email_address = user_data_with_email_address_operation.create
    user_identifier_with_hashed_email = client.get_type("UserIdentifier")
    # Hash normalized email addresses based on SHA-256 hashing algorithm.
    user_identifier_with_hashed_email.hashed_email = _normalize_and_hash(
        "customer@example.com"
    )
    user_data_with_email_address.user_identifiers.append(
        user_identifier_with_hashed_email
    )

    # Creates a second user data based on a physical address.
    user_data_with_physical_address_operation = client.get_type(
        "OfflineUserDataJobOperation"
    )
    user_data_with_physical_address = (
        user_data_with_physical_address_operation.create
    )
    user_identifier_with_address = client.get_type("UserIdentifier")
    # First and last name must be normalized and hashed.
    user_identifier_with_address.address_info.hashed_first_name = (
        _normalize_and_hash("John")
    )
    user_identifier_with_address.address_info.hashed_last_name = (
        _normalize_and_hash("Doe")
    )
    # Country and zip codes are sent in plain text.
    user_identifier_with_address.address_info.country_code = "US"
    user_identifier_with_address.address_info.postal_code = "10011"
    user_data_with_physical_address.user_identifiers.append(
        user_identifier_with_address
    )

    return [
        user_data_with_email_address_operation,
        user_data_with_physical_address_operation,
    ]


def _check_job_status(
    client,
    customer_id,
    offline_user_data_job_resource_name,
    user_list_resource_name,
):
    """Retrieves, checks, and prints the status of the offline user data job.

    Args:
        client: The Google Ads client.
        customer_id: The customer ID for which to add the user list.
        offline_user_data_job_resource_name: The resource name of the offline
            user data job to get the status of.
        user_list_resource_name: The resource name of the customer match user
            list
    """
    query = f"""
        SELECT
          offline_user_data_job.resource_name,
          offline_user_data_job.id,
          offline_user_data_job.status,
          offline_user_data_job.type,
          offline_user_data_job.failure_reason
        FROM offline_user_data_job
        WHERE offline_user_data_job.resource_name =
          '{offline_user_data_job_resource_name}'
        LIMIT 1"""

    # Issues a search request using streaming.
    google_ads_service = client.get_service("GoogleAdsService")
    results = google_ads_service.search(customer_id=customer_id, query=query)
    offline_user_data_job = next(iter(results)).offline_user_data_job
    status_name = offline_user_data_job.status.name

    print(
        f"Offline user data job ID '{offline_user_data_job.id}' with type "
        f"'{offline_user_data_job.type_.name}' has status: {status_name}"
    )

    if status_name == "SUCCESS":
        _print_customer_match_user_list_info(
            client, customer_id, user_list_resource_name
        )
    elif status_name == "FAILED":
        print(f"\tFailure Reason: {offline_user_data_job.failure_reason}")
    elif status_name in ("PENDING", "RUNNING"):
        print(
            "To check the status of the job periodically, use the following "
            f"GAQL query with GoogleAdsService.Search: {query}"
        )


def _print_customer_match_user_list_info(
    client, customer_id, user_list_resource_name
):
    """Prints information about the Customer Match user list.

    Args:
        client: The Google Ads client.
        customer_id: The customer ID for which to add the user list.
        user_list_resource_name: The resource name of the user list to which to
            add users.
    """
    googleads_service_client = client.get_service("GoogleAdsService")

    # Creates a query that retrieves the user list.
    query = f"""
        SELECT
          user_list.size_for_display,
          user_list.size_for_search
        FROM user_list
        WHERE user_list.resource_name = '{user_list_resource_name}'"""

    # Issues a search request.
    search_results = googleads_service_client.search(
        customer_id=customer_id, query=query
    )

    # Prints out some information about the user list.
    user_list = next(iter(search_results)).user_list
    print(
        "The estimated number of users that the user list "
        f"'{user_list.resource_name}' has is "
        f"{user_list.size_for_display} for Display and "
        f"{user_list.size_for_search} for Search."
    )
    print(
        "Reminder: It may take several hours for the user list to be "
        "populated. Estimates of size zero are possible."
    )


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

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

    Returns:
        A normalized (lowercase, remove 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="v7")

    parser = argparse.ArgumentParser(
        description="Adds a customer match user list for specified customer."
    )
    # 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(
        "-s",
        "--skip_polling",
        action="store_true",
        help="Whether the example should skip polling the API for completion, "
        "which can take several hours. If the '-s' flag is set the example "
        "will demonstrate how to use a search query to check the status of "
        "the uploaded user list.",
    )

    args = parser.parse_args()

    try:
        main(googleads_client, args.customer_id, args.skip_polling)
    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 2020 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.
#
# This example uses Customer Match to create a new user list (a.k.a. audience)
# and adds users to it.
#
# Note: It may take up to several hours for the list to be populated with users.
# Email addresses must be associated with a Google account.
# For privacy purposes, the user list size will show as zero until the list has
# at least 1,000 users. After that, the size will be rounded to the two most
# significant digits.

require 'optparse'
require 'google/ads/google_ads'
require 'date'
require 'digest'

def add_customer_match_user_list(customer_id)
  client = Google::Ads::GoogleAds::GoogleAdsClient.new

  user_list = create_customer_match_user_list(client, customer_id)
  add_users_to_customer_match_user_list(client, customer_id, user_list)
  print_customer_match_user_list(client, customer_id, user_list)
end

def create_customer_match_user_list(client, customer_id)
  # Creates the user list.
  operation = client.operation.create_resource.user_list do |ul|
    ul.name = "Customer Match List #{(Time.new.to_f * 1000).to_i}"
    ul.description = "A list of customers that originated from email and " \
      "physical addresses"
    # Customer Match user lists can use a membership life span of 10000 to
    # indicate unlimited; otherwise normal values apply.
    # Sets the membership life span to 30 days.
    ul.membership_life_span = 30
    ul.crm_based_user_list = client.resource.crm_based_user_list_info do |crm|
      crm.upload_key_type = :CONTACT_INFO
    end
  end

  # Issues a mutate request to add the user list and prints some information.
  response = client.service.user_list.mutate_user_lists(
    customer_id: customer_id,
    operations: [operation],
  )

  # Prints out some information about the newly created user list.
  resource_name = response.results.first.resource_name
  puts "User list with resource name #{resource_name} was created."

  resource_name
end

def add_users_to_customer_match_user_list(client, customer_id, user_list)
  # Creates the offline user data job.
  offline_user_data_job = client.resource.offline_user_data_job do |job|
    job.type = :CUSTOMER_MATCH_USER_LIST
    job.customer_match_user_list_metadata =
      client.resource.customer_match_user_list_metadata do |m|
        m.user_list = user_list
      end
  end

  offline_user_data_service = client.service.offline_user_data_job

  # Issues a request to create the offline user data job.
  response = offline_user_data_service.create_offline_user_data_job(
    customer_id: customer_id,
    job: offline_user_data_job,
  )
  offline_user_data_job_resource_name = response.resource_name
  puts "Created an offline user data job with resource name: " \
    "#{offline_user_data_job_resource_name}"

  # Issues a request to add the operations to the offline user data job.
  response = offline_user_data_service.add_offline_user_data_job_operations(
    resource_name: offline_user_data_job_resource_name,
    enable_partial_failure: true,
    operations: build_offline_user_data_job_operations(client),
  )

  # Prints errors if any partial failure error is returned.
  if response.partial_failure_error
    failures = client.decode_partial_failure_error(response.partial_failure_error)
    failures.each do |failure|
      failure.errors.each do |error|
        human_readable_error_path = error
          .location
          .field_path_elements
          .map { |location_info|
            if location_info.index
              "#{location_info.field_name}[#{location_info.index}]"
            else
              "#{location_info.field_name}"
            end
          }.join(" > ")

        errmsg =  "error occured while adding operations " \
          "#{human_readable_error_path}" \
          " with value: #{error.trigger.string_value}" \
          " because #{error.message.downcase}"
        puts errmsg
      end
    end
  end
  puts "The operations are added to the offline user data job."

  # Issues an asynchronous request to run the offline user data job
  # for executing all added operations.
  response = offline_user_data_service.run_offline_user_data_job(
    resource_name: offline_user_data_job_resource_name
  )
  puts "Asynchronous request to execute the added operations started."
  puts "Waiting until operation completes."

  # The wait_until_done! method implements a default backoff policy for
  # retrying.
  # You can also use operation.refresh! to make a call to the API to check
  # whether the LRO is finished, and operation.done? after refreshing to check
  # the status, if you'd rather implement your own backoff logic.
  response.wait_until_done! do |op|
    raise op.results.message if response.error?
  end

  puts "Offline user data job with resource name " \
    "#{offline_user_data_job_resource_name} has finished"
end

def print_customer_match_user_list(client, customer_id, user_list)
  query = <<~EOQUERY
    SELECT user_list.size_for_display, user_list.size_for_search
    FROM user_list
    WHERE user_list.resource_name = #{user_list}
  EOQUERY

  response = client.service.google_ads.search_stream(
    customer_id: customer_id,
    query: query,
  )
  row = response.first
  puts "The estimated number of users that the user list " \
    "#{row.user_list.resource_name} has is " \
    "#{row.user_list.size_for_display} for Display and " \
    "#{row.user_list.size_for_search} for Search."
  puts "Reminder: It may take several hours for the user list to be " \
    "populated with the users so getting zeros for the estimations is expected."
end

def build_offline_user_data_job_operations(client)
  operations = []

  # Creates a first user data based on an email address.
  operations << client.operation.create_resource.offline_user_data_job do |u|
    u.user_identifiers << client.resource.user_identifier do |uid|
      # Hash normalized email addresses based on SHA-256 hashing algorithm.
      uid.hashed_email = normalize_and_hash("customer@example.com")
    end
  end

  # Creates a second user data based on a physical address.
  operations << client.operation.create_resource.offline_user_data_job do |u|
    u.user_identifiers << client.resource.user_identifier do |uid|
      uid.address_info = client.resource.offline_user_address_info do |a|
        # First and last name must be normalized and hashed.
        a.hashed_first_name = normalize_and_hash("John")
        a.hashed_last_name = normalize_and_hash("Doe")
        # Country code and zip code are sent in plain text.
        a.country_code = "US"
        a.postal_code = "10011"
      end
    end
  end

  operations
end

def normalize_and_hash(str)
  Digest::SHA256.hexdigest(str.strip.downcase)
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'

  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.separator ''
    opts.separator 'Help:'

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

  begin
    add_customer_match_user_list(options.fetch(:customer_id).tr("-", ""))
  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
    raise
  end
end


      

Perl

#!/usr/bin/perl -w
#
# Copyright 2020, 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.
#
# This example uses Customer Match to create a new user list (a.k.a. audience)
# and adds users to it.
#
# Note: It may take up to several hours for the list to be populated with users.
# Email addresses must be associated with a Google account. For privacy purposes,
# the user list size will show as zero until the list has at least 1,000 users.
# After that, the size will be rounded to the two most significant digits.

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::Utils::SearchStreamHandler;
use Google::Ads::GoogleAds::V7::Resources::UserList;
use Google::Ads::GoogleAds::V7::Resources::OfflineUserDataJob;
use Google::Ads::GoogleAds::V7::Common::CrmBasedUserListInfo;
use Google::Ads::GoogleAds::V7::Common::CustomerMatchUserListMetadata;
use Google::Ads::GoogleAds::V7::Common::UserData;
use Google::Ads::GoogleAds::V7::Common::UserIdentifier;
use Google::Ads::GoogleAds::V7::Common::OfflineUserAddressInfo;
use Google::Ads::GoogleAds::V7::Enums::CustomerMatchUploadKeyTypeEnum
  qw(CONTACT_INFO);
use Google::Ads::GoogleAds::V7::Enums::OfflineUserDataJobTypeEnum
  qw(CUSTOMER_MATCH_USER_LIST);
use Google::Ads::GoogleAds::V7::Services::UserListService::UserListOperation;
use
  Google::Ads::GoogleAds::V7::Services::OfflineUserDataJobService::OfflineUserDataJobOperation;
use
  Google::Ads::GoogleAds::V7::Services::GoogleAdsService::SearchGoogleAdsStreamRequest;

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

use constant POLL_FREQUENCY_SECONDS => 1;
use constant POLL_TIMEOUT_SECONDS   => 60;

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

sub add_customer_match_user_list {
  my ($api_client, $customer_id) = @_;

  my $user_list_resource_name =
    create_customer_match_user_list($api_client, $customer_id);
  add_users_to_customer_match_user_list($api_client, $customer_id,
    $user_list_resource_name);
  print_customer_match_user_list_info($api_client, $customer_id,
    $user_list_resource_name);

  return 1;
}

# Creates a Customer Match user list.
sub create_customer_match_user_list {
  my ($api_client, $customer_id) = @_;

  # Create the user list.
  my $user_list = Google::Ads::GoogleAds::V7::Resources::UserList->new({
      name        => "Customer Match list #" . uniqid(),
      description =>
        "A list of customers that originated from email and physical addresses",
      # Customer Match user lists can use a membership life span of 10000 to
      # indicate unlimited; otherwise normal values apply.
      # Set the membership life span to 30 days.
      membershipLifeSpan => 30,
      crmBasedUserList   =>
        Google::Ads::GoogleAds::V7::Common::CrmBasedUserListInfo->new({
          uploadKeyType => CONTACT_INFO
        })});

  # Create the user list operation.
  my $user_list_operation =
    Google::Ads::GoogleAds::V7::Services::UserListService::UserListOperation->
    new({
      create => $user_list
    });

  # Issue a mutate request to add the user list and print some information.
  my $user_lists_response = $api_client->UserListService()->mutate({
      customerId => $customer_id,
      operations => [$user_list_operation]});
  my $user_list_resource_name =
    $user_lists_response->{results}[0]{resourceName};
  printf "User list with resource name '%s' was created.\n",
    $user_list_resource_name;

  return $user_list_resource_name;
}

# Creates and executes an asynchronous job to add users to the Customer Match
# user list.
sub add_users_to_customer_match_user_list {
  my ($api_client, $customer_id, $user_list_resource_name) = @_;

  my $offline_user_data_job_service = $api_client->OfflineUserDataJobService();

  # Create a new offline user data job.
  my $offline_user_data_job =
    Google::Ads::GoogleAds::V7::Resources::OfflineUserDataJob->new({
      type                          => CUSTOMER_MATCH_USER_LIST,
      customerMatchUserListMetadata =>
        Google::Ads::GoogleAds::V7::Common::CustomerMatchUserListMetadata->new({
          userList => $user_list_resource_name
        })});

  # Issue a request to create the offline user data job.
  my $create_offline_user_data_job_response =
    $offline_user_data_job_service->create({
      customerId => $customer_id,
      job        => $offline_user_data_job
    });
  my $offline_user_data_job_resource_name =
    $create_offline_user_data_job_response->{resourceName};
  printf
    "Created an offline user data job with resource name: '%s'.\n",
    $offline_user_data_job_resource_name;

  # Issue a request to add the operations to the offline user data job.
  my $response = $offline_user_data_job_service->add_operations({
      resourceName         => $offline_user_data_job_resource_name,
      enablePartialFailure => "true",
      operations           => build_offline_user_data_job_operations()});

  # Print 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 handle_partial_failure.pl to learn more.
  if ($response->{partialFailureError}) {
    # Extract the partial failure from the response status.
    my $partial_failure = $response->{partialFailureError}{details}[0];
    printf
      "%d partial failure error(s) occurred: %s.\n",
      scalar @{$partial_failure->{errors}},
      $response->{partialFailureError}{message};
  }
  print "The operations are added to the offline user data job.\n";

  # Issue an asynchronous request to run the offline user data job for executing
  # all added operations.
  my $operation_response = $offline_user_data_job_service->run({
    resourceName => $offline_user_data_job_resource_name
  });
  print "Asynchronous request to execute the added operations started.\n";
  print "Waiting until operation completes.\n";

  # poll_until_done() implements a default back-off policy for retrying. You can
  # tweak the parameters like the poll timeout seconds by passing them to the
  # poll_until_done() method. Visit the OperationService.pm file for more details.
  my $lro = $api_client->OperationService()->poll_until_done({
    name                 => $operation_response->{name},
    pollFrequencySeconds => POLL_FREQUENCY_SECONDS,
    pollTimeoutSeconds   => POLL_TIMEOUT_SECONDS
  });
  if ($lro->{done}) {
    printf "Offline user data job with resource name '%s' has finished.\n",
      $offline_user_data_job_resource_name;
  } else {
    printf
      "Offline user data job with resource name '%s' still pending after %d " .
      "seconds, continuing the execution of the code example anyway.\n",
      $offline_user_data_job_resource_name,
      POLL_TIMEOUT_SECONDS;
  }
}

# Builds and returns offline user data job operations to add one user identified
# by an email address and one user identified based on a physical address.
sub build_offline_user_data_job_operations() {
  # Create a first user data based on an email address.
  my $user_data_with_email_address =
    Google::Ads::GoogleAds::V7::Common::UserData->new({
      userIdentifiers => [
        Google::Ads::GoogleAds::V7::Common::UserIdentifier->new({
            # Hash normalized email addresses based on SHA-256 hashing algorithm.
            hashedEmail => normalize_and_hash('customer@example.com')})]});

  # Create a second user data based on a physical address.
  my $user_data_with_physical_address =
    Google::Ads::GoogleAds::V7::Common::UserData->new({
      userIdentifiers => [
        Google::Ads::GoogleAds::V7::Common::UserIdentifier->new({
            addressInfo =>
              Google::Ads::GoogleAds::V7::Common::OfflineUserAddressInfo->new({
                # First and last name must be normalized and hashed.
                hashedFirstName => normalize_and_hash("John"),
                hashedLastName  => normalize_and_hash("Doe"),
                # Country code and zip code are sent in plain text.
                countryCode => "US",
                postalCode  => "10011"
              })})]});

  # Create the operations to add the two users.
  my $operations = [
    Google::Ads::GoogleAds::V7::Services::OfflineUserDataJobService::OfflineUserDataJobOperation
      ->new({
        create => $user_data_with_email_address
      }
      ),
    Google::Ads::GoogleAds::V7::Services::OfflineUserDataJobService::OfflineUserDataJobOperation
      ->new({
        create => $user_data_with_physical_address
      })];

  return $operations;
}

# Prints information about the Customer Match user list.
sub print_customer_match_user_list_info {
  my ($api_client, $customer_id, $user_list_resource_name) = @_;

  # Create a query that retrieves the user list.
  my $search_query =
    "SELECT user_list.size_for_display, user_list.size_for_search " .
    "FROM user_list " .
    "WHERE user_list.resource_name = '$user_list_resource_name'";

  # Create a search Google Ads stream request that will retrieve the user list.
  my $search_stream_request =
    Google::Ads::GoogleAds::V7::Services::GoogleAdsService::SearchGoogleAdsStreamRequest
    ->new({
      customerId => $customer_id,
      query      => $search_query,
    });

  # Get the GoogleAdsService.
  my $google_ads_service = $api_client->GoogleAdsService();

  my $search_stream_handler =
    Google::Ads::GoogleAds::Utils::SearchStreamHandler->new({
      service => $google_ads_service,
      request => $search_stream_request
    });

  # Issue a search request and process the stream response to print out some
  # information about the user list.
  $search_stream_handler->process_contents(
    sub {
      my $google_ads_row = shift;
      my $user_list      = $google_ads_row->{userList};

      printf "The estimated number of users that the user list '%s' " .
        "has is %d for Display and %d for Search.\n",
        $user_list->{resourceName},
        $user_list->{sizeForDisplay},
        $user_list->{sizeForSearch};
    });

  print
    "Reminder: It may take several hours for the user list to be populated " .
    "with the users so getting zeros for the estimations is expected.\n";
}

# Normalizes and hashes a string value.
sub normalize_and_hash {
  my $value = shift;

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

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

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

# Call the example.
add_customer_match_user_list($api_client, $customer_id =~ s/-//gr);

=pod

=head1 NAME

add_customer_match_user_list

=head1 DESCRIPTION

This example uses Customer Match to create a new user list (a.k.a. audience) and
adds users to it.

Note: It may take up to several hours for the list to be populated with users.
Email addresses must be associated with a Google account. For privacy purposes,
the user list size will show as zero until the list has at least 1,000 users.
After that, the size will be rounded to the two most significant digits.

=head1 SYNOPSIS

add_customer_match_user_list.pl [options]

    -help                       Show the help message.
    -customer_id                The Google Ads customer ID.

=cut