Как начать работу со списками электронных адресов

Списки электронных адресов позволяют использовать онлайн- и офлайн-данные, чтобы показывать клиентам рекламу (в том числе для повторного вовлечения) в Поиске, Gmail, КМС, на YouTube и вкладке "Покупки". При этом объявления будет показываться и существующим клиентам, и тем, кто на них похож. Вы можете загружать данные CRM массово, добавлять или удалять их, а также использовать списки пользователей для создания logical_user_list.

В обзоре управления аудиториями приведен список различных типов сегментов аудитории, которые можно использовать для сравнения списков электронных адресов с другими вариантами списков пользователей.

Подробнее о списках электронных адресов и выборе целевой аудитории…

Требования

Функция "Списки электронных адресов" доступна не во всех аккаунтах. Чтобы вы могли использовать списки электронных адресов, у вашего аккаунта должны быть:

  • хорошая репутация в отношении соблюдения правил;
  • хорошая репутация в отношении оплаты рекламных услуг;

Набор доступных функций зависит от того, какие условия выполняются в вашем аккаунте. Ознакомьтесь с правилами в отношении списков электронных адресов, чтобы узнать о требованиях и ограничениях.

Подготовка к реализации

Прежде чем писать код, необходимо спланировать реализацию функции "Списки электронных адресов". В этом разделе описан общий процесс и ключевые факторы, которые помогут вам создать надежную интеграцию. После того как вы составите план, можно переходить к следующим шагам.

Порядок использования

Рекомендуем следующий порядок действий при создании списка клиентов и настройке таргетинга на него:

  1. Создайте пустой список клиентов.

  2. Создайте OfflineUserDataJob. Создать одну большую задачу гораздо эффективнее, чем несколько маленьких.

    Убедитесь, что вы заполняете поле consent объекта customer_match_user_list_metadata в запросах OfflineUserDataJob create. Для запросов remove согласие не требуется. API возвращает значение OfflineUserDataJobError.CUSTOMER_NOT_ACCEPTED_CUSTOMER_DATA_TERMS, если для параметра consent.ad_user_data или consent.ad_personalization задания задано значение DENIED.

    Если пользователь не дал согласия, вы можете создать задание с операцией remove, чтобы удалить идентификаторы пользователя из списка.

    Если у вас нет согласия для определенных пользователей, создайте отдельное задание, в котором не задано поле consent объекта customer_match_user_list_metadata, а затем добавьте идентификаторы этих пользователей с помощью операций create для этого отдельного задания.

  3. Добавляйте операции с помощью метода OfflineUserDataJobService.AddOfflineUserDataJobOperations. Мы рекомендуем добавлять не более 10 000 идентификаторов в один вызов для оптимальной обработки. Один запрос AddOfflineUserDataJobOperations может содержать не более 100 000 идентификаторов во всех объектах UserData в списке операций.

    Например, если у каждого из ваших объектов UserData есть один объект UserIdentifier для hashed_email и ещё один объект UserIdentifier для hashed_phone_number, то оптимальным будет отправлять 5000 объектов UserData в одном запросе, поскольку в каждом запросе будет 10 000 идентификаторов пользователей.

  4. Повторяйте предыдущий шаг, пока не добавите все операции или пока задание не будет выполнено. Количество операций, которые можно добавить в одно задание,не ограничено,но для оптимальной обработки мы рекомендуем не более 1 000 000 операций на задание.

  5. Запустите задание. Задание необходимо запустить в течение пяти дней после создания. В противном случае задание нельзя будет запустить.

  6. Опрос для успешной загрузки.

  7. Проверьте коэффициент соответствия.

  8. Настройте таргетинг на список.

OfflineUserDataJobService и UserDataService

Загрузить списки электронных адресов можно двумя способами. Выберите сервис в зависимости от вашего варианта использования, поскольку для некоторых сервисов могут действовать ограничения.

Сервисы для загрузки списков электронных адресов
OfflineUserDataJobService (предпочтительно) Большинство разработчиков используют этот сервис. Он оптимизирован для загрузки больших объемов данных с высокой пропускной способностью и возвращает показатели успешности после завершения. В этом руководстве основное внимание уделяется именно этому сервису.
UserDataService Этот сервис оптимизирован для загрузки небольшого количества идентификаторов с нерегулярными обновлениями и не предназначен для непрерывной работы. В одном запросе может быть не более 10 операций. Кроме того, в одном запросе не может быть более 100 объектов во всех user_identifiers.

Инструкции по загрузке с помощью этого сервиса можно найти в руководстве по управлению интеграцией списков электронных адресов.

В запросах create необходимо заполнить поле consent объекта customer_match_user_list_metadata из объекта UploadUserDataRequest. Для запросов remove согласие не требуется. Если параметр consent.ad_user_data или consent.ad_personalization имеет значение DENIED, API возвращает ответ с параметром received_operations_count, равным нулю, что означает, что запрос не был обработан. Если у идентификатора есть согласие DENIED, вы можете запросить удаление идентификатора из списка пользователей с помощью операции remove из UploadUserDataRequest.

Рекомендации

При интеграции списков электронных адресов следуйте приведенным ниже рекомендациям.

  • Не пытайтесь использовать несколько аккаунтов для изменения одного списка пользователей. Изменить список пользователей может только аккаунт Google Рекламы или партнера по обработке данных, в котором он был создан.

  • Увеличьте количество операций на AddOfflineUserDataJobOperationsRequest до 100 000 идентификаторов, чтобы избежать ошибок RESOURCE_EXHAUSTED.

  • Не смешивайте операции create и remove в одном элементе OfflineUserDataJob. В противном случае может возникнуть ошибка CONFLICTING_OPERATION.

  • Включите partial_failure в AddOfflineUserDataJobOperationsRequest, чтобы выявлять проблемные операции до запуска задания. Операции проверяются при загрузке в OfflineUserDataJob.

  • Чтобы избежать ошибок CONCURRENT_MODIFICATION при обновлении списков пользователей:

    Не допускайте одновременного выполнения нескольких процессов OfflineUserDataJob в состоянии RUNNING, если они нацелены на одно и то же название ресурса списка пользователей, указанное в CustomerMatchUserListMetadata.user_list. Ошибка возникает, потому что несколько заданий пытаются изменить один и тот же список одновременно. Конфликт также может возникнуть, если вы измените список через интерфейс Google Рекламы, пока выполняется задание API.

    Это ограничение применяется к заданиям, которые были запущены, то есть находятся в состоянии RUNNING. При этом можно безопасно отправлять несколько запросов AddOfflineUserDataJobOperations, чтобы добавить данные в один объект OfflineUserDataJob, пока он находится в состоянии PENDING. Операции можно добавлять в задание в любое время до вызова RunOfflineUserDataJob для запуска процесса.

  • Если вам нужно выполнить тысячи операций, создайте один файл OfflineUserDataJob, содержащий все операции. Не создавайте несколько заданий, каждое из которых содержит всего несколько сотен операций, и не запускайте их последовательно или одновременно. Одна большая задача со всеми операциями гораздо эффективнее нескольких небольших задач и снижает вероятность ошибок в рабочем процессе.

Ознакомьтесь с рекомендациями по использованию списков электронных адресов, чтобы узнать, как оптимизировать таргетинг.

Шаг 1. Создайте список клиентов

Создайте список клиентов с помощью UserListService. Списки клиентов создаются путем установки поля crm_based_user_list в объекте user_list. Поле crm_based_user_list можно задать для типов кампаний, поддерживающих таргетинг на списки клиентов:

Списки электронных адресов в кампаниях разных типов
Поисковая сеть Объявления показываются в поисковой сети Google.
Контекстно-медийная сеть Объявления показываются в контекстно-медийной сети Google.
Возможность показа в КМС для кампаний в поисковой сети Объявления показываются в поисковой и контекстно-медийной сети Google.
Кампании для создания спроса Объявления показываются на YouTube, в рекомендациях и Gmail (таргетинг выполняется с помощью ресурса Audience на AdGroupCriterion.audience).
Кампании с максимальной эффективностью Объявления показываются во всех каналах Google (используются как сигнал аудитории на сайте AssetGroupSignal.audience).
Видеокампании Объявления показываются на YouTube.
Торговые кампании Реклама показывается на вкладке "Покупки".

crm_based_user_list содержит три поля:

  • app_id: строка, которая однозначно идентифицирует мобильное приложение, из которого были собраны данные. Это необходимо при создании CrmBasedUserList для загрузки мобильных рекламных идентификаторов.

  • upload_key_type – тип ключа, соответствующий списку. Может принимать значения CONTACT_INFO, CRM_ID или MOBILE_ADVERTISING_ID. В одном списке нельзя использовать разные типы данных. Это поле обязательно для всех списков клиентов.

  • data_source_type – источник данных списка. Значение по умолчанию – FIRST_PARTY. Клиенты, добавленные в белый список, могут создавать списки клиентов, полученные от третьих лиц (THIRD_PARTY_CREDIT_BUREAU, THIRD_PARTY_VOTER_FILE или THIRD_PARTY_PARTNER_DATA). Только списки, полученные от FIRST_PARTY, можно объединять с другими списками.

Атрибут membership_life_span списка пользователей позволяет задать период времени в днях, в течение которого пользователь считается находящимся в списке. Значение параметра "Срок хранения" membership_life_span в списке электронных адресов не должно превышать 540. Это значение задано по умолчанию.

Атрибут membership_status определяет, можно ли добавлять в список новых пользователей.

Пример кода для создания списка клиентов

Java

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")
          // Membership life span must be between 0 and 540 days inclusive. See:
          // https://developers.google.com/google-ads/api/reference/rpc/latest/UserList#membership_life_span
          // 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 a CREATE 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();
  }
}

      

C#

private string CreateCustomerMatchUserList(GoogleAdsClient client, long customerId)
{
    // Get the UserListService.
    UserListServiceClient service = client.GetService(Services.V25.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",
        // Membership life span must be between 0 and 540 days inclusive. See:
        // https://developers.google.com/google-ads/api/reference/rpc/latest/UserList#membership_life_span
        // 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;
}
      

PHP

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',
        // Membership life span must be between 0 and 540 days inclusive. See:
        // https://developers.google.com/google-ads/api/reference/rpc/latest/UserList#membership_life_span
        // Sets the membership life span to 30 days.
        'membership_life_span' => 30,
        'crm_based_user_list' => new CrmBasedUserListInfo(
            [
            // 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 a CREATE
            // operation.
            '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(
        MutateUserListsRequest::build($customerId, [$operation])
    );
    $userListResourceName = $response->getResults()[0]->getResourceName();
    printf("User list with resource name '%s' was created.%s", $userListResourceName, PHP_EOL);

    return $userListResourceName;
}
      

Python

def create_customer_match_user_list(
    client: GoogleAdsClient, customer_id: str
) -> str:
    """Creates a Customer Match user list.

    Args:
        client: The Google Ads client.
        customer_id: The ID for the customer that owns the user list.

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

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

    # Creates the new user list.
    user_list: UserList = 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"
    )
    # Sets the upload key type to indicate the type of identifier that is used
    # to add users to the list. This field is immutable and required for a
    # CREATE operation.
    user_list.crm_based_user_list.upload_key_type = (
        client.enums.CustomerMatchUploadKeyTypeEnum.CONTACT_INFO
    )
    # Membership life span must be between 0 and 540 days inclusive. See:
    # https://developers.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: MutateUserListsResponse = (
        user_list_service_client.mutate_user_lists(
            customer_id=customer_id, operations=[user_list_operation]
        )
    )
    user_list_resource_name: str = response.results[0].resource_name
    print(
        f"User list with resource name '{user_list_resource_name}' was created."
    )

    return user_list_resource_name
      

Ruby

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"
    # Membership life span must be between 0 and 540 days inclusive. See:
    # https://developers.google.com/google-ads/api/reference/rpc/latest/UserList#membership_life_span
    # 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
      

Perl

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

  # Create the user list.
  my $user_list = Google::Ads::GoogleAds::V25::Resources::UserList->new({
      name        => "Customer Match list #" . uniqid(),
      description =>
        "A list of customers that originated from email and physical addresses",
      # Membership life span must be between 0 and 540 days inclusive. See:
      # https://developers.google.com/google-ads/api/reference/rpc/latest/UserList#membership_life_span
      # Set the membership life span to 30 days.
      membershipLifeSpan => 30,
      # Set 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
      # a CREATE operation.
      crmBasedUserList =>
        Google::Ads::GoogleAds::V25::Common::CrmBasedUserListInfo->new({
          uploadKeyType => CONTACT_INFO
        })});

  # Create the user list operation.
  my $user_list_operation =
    Google::Ads::GoogleAds::V25::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;
}
      

curl

Шаг 2. Добавьте пользователей в список

Три основных ключа сопоставления – это адрес электронной почты, почтовый адрес и номер телефона. В качестве ключей сопоставления можно использовать идентификаторы пользователей и мобильных устройств, но эти решения менее перспективны, поскольку они основаны на файлах cookie и идентификаторах устройств. Мы рекомендуем по возможности загружать контактную информацию пользователей, например адреса электронной почты, почтовые адреса и номера телефонов, вместо идентификаторов CRM или мобильных устройств.

Каждый список пользователей может содержать только один тип данных клиентов, указанный в поле CrmBasedUserListInfo.upload_key_type. Кроме того, объект UserData, представляющий одного пользователя, может содержать до 20 идентификаторов пользователей, каждый из которых имеет собственный объект UserIdentifier. Если указать более 20 идентификаторов, появится ошибка TOO_MANY_USER_IDENTIFIERS.

Google Реклама использует список электронных адресов для таргетинга, только если во время показа объявления в нем есть минимальное количество активных пользователей (то есть пользователей из вашего списка, которые в данный момент активны в Gmail, поисковой или контекстно-медийной сетях Google, а также на YouTube). Загрузите не менее 5000 участников,чтобы повысить вероятность того, что будет достаточно сопоставленных активных пользователей для таргетинга.

Как загрузить контактную информацию пользователей

Чтобы загрузить адреса электронной почты, почтовые адреса или номера телефонов пользователей, задайте для параметра upload_key_type значение CONTACT_INFO. Контактная информация должна быть связана с аккаунтом Google, чтобы ее можно было сопоставить. Корпоративные аккаунты, например Google Workspace, не могут быть целевыми.

Чтобы сохранить конфиденциальность данных, адреса электронной почты, имена, фамилии и номера телефонов перед загрузкой необходимо хешировать, используя алгоритм SHA-256. Чтобы стандартизировать результаты работы функции хеширования, перед ее запуском выполните следующие действия:

Если контактная информация неправильно отформатирована до хеширования, API примет хешированные данные, но не сможет сопоставить их с клиентом.

Если вы хотите загрузить данные о почтовых адресах, вам необходимо указать хотя бы:

  • Код страны
  • Почтовый индекс
  • Хешированное имя
  • Хешированная фамилия

Если какое-либо из этих полей отсутствует, адрес не будет сопоставлен.

В списке клиентов может быть только один upload_key_type, но для одного upload_key_type типа CONTACT_INFO можно загрузить несколько типов контактной информации. Это поможет повысить коэффициент соответствия.

Форматирование адресов электронной почты для расширенного отслеживания конверсий

Чтобы обеспечить максимальный коэффициент соответствия при расширенном отслеживании конверсий, необходимо правильно отформатировать и хешировать адреса электронной почты. Для этого выполните следующие действия:

  1. Применить стандартную нормализацию (для всех адресов электронной почты)

    • Удалите начальные и конечные пробельные символы ASCII.
    • Все символы в адресе электронной почты приведены в нижнем регистре.
  2. Примените нормализацию для домена (только для gmail.com и googlemail.com):

    • После того как вы переведете адрес электронной почты в нижний регистр, проверьте, является ли домен (после символа @) gmail.com или googlemail.com.
    • Удалите точки (.). В части имени пользователя (до символа @) удалите все точки.
    • Удалите суффиксы со знаком плюса (+). В части имени пользователя удалите первый знак плюса (+) и все последующие символы до знака @.
      • Пример: Jane.Doe+Shopping@googlemail.com сначала преобразуется в jane.doe+shopping@googlemail.com (нижний регистр), а затем в janedoe@googlemail.com (правила Gmail и Googlemail).
    • Другие домены. Для любого домена, кроме gmail.com или googlemail.com, не удаляйте точки и суффиксы с плюсом из имени пользователя. Применяйте только стандартную нормализацию (удаление пробелов и преобразование в нижний регистр).
      • Пример: user.name+NYC@Example.com нормализуется в user.name+nyc@example.com.
  3. Хеширование нормализованного адреса электронной почты

    • Закодируйте нормализованную строку адреса электронной почты в байты UTF-8.
    • Захешируйте байты UTF-8 с помощью алгоритма SHA-256.
    • Полученный дайджест должен быть отформатирован как шестнадцатеричная строка в нижнем регистре (64 символа).
import hashlib
import re


def normalize_and_hash_email(email: str) -> str:
    """Normalizes and SHA-256 hashes an email address for enhanced conversions."""
    normalized = email.strip().lower()
    parts = normalized.split("@")
    if len(parts) != 2:
        raise ValueError("Invalid email address format.")
    username, domain = parts
    if domain in ("gmail.com", "googlemail.com"):
        username = username.replace(".", "")
        username = re.sub(r"\+.*$", "", username)
    normalized = f"{username}@{domain}"
    return hashlib.sha256(normalized.encode("utf-8")).hexdigest()

Как загрузить идентификаторы CRM

Чтобы заполнить список клиентов идентификаторами CRM, задайте для параметра upload_key_type значение CRM_ID. Идентификаторы CRM сопоставляются с идентификаторами пользователей, которые генерируются и назначаются рекламодателем. Это похоже на загрузку MOBILE_ADVERTISING_ID экземпляров, но вместо этого вы заполняете поле third_party_user_id объекта UserIdentifier.

Как загрузить идентификаторы мобильных устройств

Как и в случае со списками электронных адресов, вы можете создавать списки на основе идентификаторов мобильных устройств, таких как Identifier for Advertising (IDFA) или Google Advertising ID (AAID). Для этого укажите свойство app_id и задайте для параметра upload_key_type значение MOBILE_ADVERTISING_ID, прежде чем использовать список пользователей для сопоставления клиентов с идентификаторами мобильных устройств.

Пример кода

В примере ниже используется OfflineUserDataJobOperation, чтобы добавить контактную информацию клиента в список клиентов.

Java

// Creates a raw input list of unhashed user information, where each element of the list
// represents a single user and is a map containing a separate entry for the keys "email",
// "phone", "firstName", "lastName", "countryCode", and "postalCode". In your application, this
// data might come from a file or a database.
List<Map<String, String>> rawRecords = new ArrayList<>();
// The first user data has an email address and a phone number.
Map<String, String> rawRecord1 =
    ImmutableMap.<String, String>builder()
        .put("email", "dana@example.com")
        // Phone number to be converted to E.164 format, with a leading '+' as required. This
        // includes whitespace, dashes, and parentheses that will be removed later.
        .put("phone", "+1 (800) 555-0101")
        .build();
// The second user data has an email address, a mailing address, and a phone number.
Map<String, String> rawRecord2 =
    ImmutableMap.<String, String>builder()
        // Email address that includes a period (.) and plus (+) suffix before the Gmail domain.
        .put("email", "alex.2+myalias@gmail.com")
        // Address that includes all four required elements: first name, last name, country
        // code, and postal code.
        .put("firstName", "Alex")
        .put("lastName", "Quinn")
        .put("countryCode", "US")
        .put("postalCode", "94045")
        // Phone number to be converted to E.164 format, with a leading '+' as required.
        .put("phone", "+1 800-555-0102")
        .build();
// The third user data only has an email address.
Map<String, String> rawRecord3 =
    ImmutableMap.<String, String>builder().put("email", "charlie@example.com").build();
// Adds the raw records to the raw input list.
rawRecords.add(rawRecord1);
rawRecords.add(rawRecord2);
rawRecords.add(rawRecord3);

// Iterates over the raw input list and creates a UserData object for each record.
List<UserData> userDataList = new ArrayList<>();
for (Map<String, String> rawRecord : rawRecords) {
  // Creates a builder for the UserData object that represents a member of the user list.
  UserData.Builder userDataBuilder = UserData.newBuilder();
  // Checks if the record has email, phone, or address information, and adds a SEPARATE
  // UserIdentifier object for each one found. For example, a record with an email address and a
  // phone number will result in a UserData with two UserIdentifiers.

  // 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();
  //
  // The separate 'if' statements below demonstrate the correct approach for creating a UserData
  // for a member with multiple UserIdentifiers.

  // Checks if the record has an email address, and if so, adds a UserIdentifier for it.
  if (rawRecord.containsKey("email")) {
    UserIdentifier hashedEmailIdentifier =
        UserIdentifier.newBuilder()
            .setHashedEmail(normalizeAndHashEmailAddress(sha256Digest, rawRecord.get("email")))
            .build();
    // Adds the hashed email identifier to the UserData object's list.
    userDataBuilder.addUserIdentifiers(hashedEmailIdentifier);
  }

  // Checks if the record has a phone number, and if so, adds a UserIdentifier for it.
  if (rawRecord.containsKey("phone")) {
    UserIdentifier hashedPhoneNumberIdentifier =
        UserIdentifier.newBuilder()
            .setHashedPhoneNumber(
                normalizeAndHashPhoneNumber(sha256Digest, rawRecord.get("phone")))
            .build();
    // Adds the hashed phone number identifier to the UserData object's list.
    userDataBuilder.addUserIdentifiers(hashedPhoneNumberIdentifier);
  }

  // Checks if the record has all the required mailing address elements, and if so, adds a
  // UserIdentifier for the mailing address.
  if (rawRecord.containsKey("firstName")) {
    // Checks if the record contains all the other required elements of a mailing address.
    Set<String> missingAddressKeys = new HashSet<>();
    for (String addressKey : new String[] {"lastName", "countryCode", "postalCode"}) {
      if (!rawRecord.containsKey(addressKey)) {
        missingAddressKeys.add(addressKey);
      }
    }

    if (!missingAddressKeys.isEmpty()) {
      System.out.printf(
          "Skipping addition of mailing address information because the following required keys"
              + " are missing: %s%n",
          missingAddressKeys);
    } else {
      // Creates an OfflineUserAddressInfo object that contains all the required elements of a
      // mailing address.
      OfflineUserAddressInfo addressInfo =
          OfflineUserAddressInfo.newBuilder()
              .setHashedFirstName(
                  normalizeAndHash(sha256Digest, rawRecord.get("firstName"), false))
              .setHashedLastName(
                  normalizeAndHash(sha256Digest, rawRecord.get("lastName"), false))
              .setCountryCode(rawRecord.get("countryCode"))
              .setPostalCode(rawRecord.get("postalCode"))
              .build();
      UserIdentifier addressIdentifier =
          UserIdentifier.newBuilder().setAddressInfo(addressInfo).build();
      // Adds the address identifier to the UserData object's list.
      userDataBuilder.addUserIdentifiers(addressIdentifier);
    }
  }

  if (!userDataBuilder.getUserIdentifiersList().isEmpty()) {
    // Builds the UserData and adds it to the list.
    userDataList.add(userDataBuilder.build());
  }
}

// Creates the operations to add users.
List<OfflineUserDataJobOperation> operations = new ArrayList<>();
for (UserData userData : userDataList) {
  operations.add(OfflineUserDataJobOperation.newBuilder().setCreate(userData).build());
}
      

C#

// Creates a raw input list of unhashed user information, where each element of the list
// represents a single user and is a map containing a separate entry for the keys
// "email", "phone", "firstName", "lastName", "countryCode", and "postalCode".
// In your application, this data might come from a file or a database.
List<Dictionary<string, string>> rawRecords = new List<Dictionary<string, string>>();

// The first user data has an email address and a phone number.
Dictionary<string, string> rawRecord1 = new Dictionary<string, string>();
rawRecord1.Add("email", "dana@example.com");
// Phone number to be converted to E.164 format, with a leading '+' as required.
// This includes whitespace that will be removed later.
rawRecord1.Add("phone", "+1 800 5550101");

// The second user data has an email address, a mailing address, and a phone number.
Dictionary<string, string> rawRecord2 = new Dictionary<string, string>();
// Email address that includes a period (.) before the Gmail domain.
rawRecord2.Add("email", "alex.2@example.com");
// Address that includes all four required elements: first name, last name, country
// code, and postal code.
rawRecord2.Add("firstName", "Alex");
rawRecord2.Add("lastName", "Quinn");
rawRecord2.Add("countryCode", "US");
rawRecord2.Add("postalCode", "94045");
// Phone number to be converted to E.164 format, with a leading '+' as required.
// This includes whitespace that will be removed later.
rawRecord2.Add("phone", "+1 800 5550102");

// The third user data only has an email address.
Dictionary<string, string> rawRecord3 = new Dictionary<string, string>();
rawRecord3.Add("email", "charlie@example.com");

// Adds the raw records to the raw input list.
rawRecords.Add(rawRecord1);
rawRecords.Add(rawRecord2);
rawRecords.Add(rawRecord3);

// Iterates over the raw input list and creates a UserData object for each record.
List<UserData> userDataList = new List<UserData>();
foreach (Dictionary<string, string> rawRecord in rawRecords) {
    // Creates a UserData object that represents a member of the user list.
    UserData userData = new UserData();
    // Checks if the record has email, phone, or address information, and adds a
    // SEPARATE UserIdentifier object for each one found.
    // For example, a record with an email address and a phone number will result in a
    // UserData with two UserIdentifiers.

    // 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 = new UserIdentifier()
    // {
    //      HashedEmail = "...",
    //      HashedPhoneNumber = "..."
    // };
    //
    // The separate 'if' statements below demonstrate the correct approach for creating
    // a UserData for a member with multiple UserIdentifiers.

    // Checks if the record has an email address, and if so, adds a UserIdentifier
    // for it.
    if (rawRecord.ContainsKey("email")) {
        UserIdentifier hashedEmailIdentifier = new UserIdentifier()
        {
            HashedEmail = NormalizeAndHash(rawRecord["email"], true)
        };

        userData.UserIdentifiers.Add(hashedEmailIdentifier);
    }

    // Checks if the record has a phone number, and if so, adds a UserIdentifier for it.
    if (rawRecord.ContainsKey("phone")) {
        UserIdentifier hashedPhoneNumberIdentifier = new UserIdentifier()
        {
            HashedPhoneNumber = NormalizeAndHash(rawRecord["phone"], true)
        };

        // Adds the hashed phone number identifier to the UserData object's list.
        userData.UserIdentifiers.Add(hashedPhoneNumberIdentifier);
    }

    // Checks if the record has all the required mailing address elements, and if so,
    // adds a UserIdentifier for the mailing address.
    if (rawRecord.ContainsKey("firstName")) {
        // Checks if the record contains all the other required elements of a mailing
        // address.
        HashSet<string> missingAddressKeys = new HashSet<string>();
        foreach (string addressKey in new string[] {"lastName", "countryCode",
            "postalCode"}) {
        if (!rawRecord.ContainsKey(addressKey)) {
            missingAddressKeys.Add(addressKey);
        }
        }

        if (!missingAddressKeys.Any()) {
        Console.WriteLine(
            $"Skipping addition of mailing address information because the following " +
                "required keys are missing: {missingAddressKeys}");
        } else {
            // Creates an OfflineUserAddressInfo object that contains all the required
            // elements of a mailing address.
            OfflineUserAddressInfo addressInfo = new OfflineUserAddressInfo()
            {
                HashedFirstName = NormalizeAndHash(rawRecord["firstName"]),
                HashedLastName = NormalizeAndHash(rawRecord["lastName"]),
                CountryCode = rawRecord["countryCode"],
                PostalCode = rawRecord["postalCode"]
            };

            UserIdentifier addressIdentifier = new UserIdentifier()
            {
                AddressInfo = addressInfo
            };

            // Adds the address identifier to the UserData object's list.
            userData.UserIdentifiers.Add(addressIdentifier);
        }
    }

    if (userData.UserIdentifiers.Any())
    {
        userDataList.Add(userData);
    }
}

// Creates the operations to add the users.
List<OfflineUserDataJobOperation> operations = new List<OfflineUserDataJobOperation>();
foreach(UserData userData in userDataList)
{
    operations.Add(new OfflineUserDataJobOperation()
    {
        Create = userData
    });
}
      

PHP

// Creates a raw input list of unhashed user information, where each element of the list
// represents a single user and is a map containing a separate entry for the keys 'email',
// 'phone', 'firstName', 'lastName', 'countryCode', and 'postalCode'. In your application,
// this data might come from a file or a database.
$rawRecords = [];
// The first user data has an email address and a phone number.
$rawRecord1 = [
    // The first user data has an email address and a phone number.
    'email' => 'dana@example.com',
    // Phone number to be converted to E.164 format, with a leading '+' as required. This
    // includes whitespace that will be removed later.
    'phone' => '+1 800 5550101'
];
$rawRecords[] = $rawRecord1;

// The second user data has an email address, a mailing address, and a phone number.
$rawRecord2 = [
    // Email address that includes a period (.) before the Gmail domain.
    'email' => 'alex.2@example.com',
    // Address that includes all four required elements: first name, last name, country
    // code, and postal code.
    'firstName' => 'Alex',
    'lastName' => 'Quinn',
    'countryCode' => 'US',
    'postalCode' => '94045',
    // Phone number to be converted to E.164 format, with a leading '+' as required.
    'phone' => '+1 800 5550102',
];
$rawRecords[] = $rawRecord2;

// The third user data only has an email address.
$rawRecord3 = ['email' => 'charlie@example.com'];
$rawRecords[] = $rawRecord3;

// Iterates over the raw input list and creates a UserData object for each record.
$userDataList = [];
foreach ($rawRecords as $rawRecord) {
    // Checks if the record has email, phone, or address information, and adds a SEPARATE
    // UserIdentifier object for each one found. For example, a record with an email address
    // and a phone number will result in a UserData with two UserIdentifiers.

    // 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'.
    //
    // $incorrectlyPopulatedUserIdentifier = new UserIdentifier();
    // $incorrectlyPopulatedUserIdentifier->setHashedEmail('...');
    // $incorrectlyPopulatedUserIdentifier->setHashedPhoneNumber('...');
    //
    // The separate 'if' statements below demonstrate the correct approach for creating a
    // UserData for a member with multiple UserIdentifiers.

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

    // 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($rawRecord['phone'], true)
            ]
        );
        // Adds the hashed email identifier to the user identifiers list.
        $userIdentifiers[] = $hashedPhoneNumberIdentifier;
    }

    // Checks if the record has all the required mailing address elements, and if so, adds a
    // UserIdentifier for the mailing address.
    if (array_key_exists('firstName', $rawRecord)) {
        // Checks if the record contains all the other required elements of a mailing
        // address.
        $missingAddressKeys = [];
        foreach (['lastName', 'countryCode', 'postalCode'] as $addressKey) {
            if (!array_key_exists($addressKey, $rawRecord)) {
                $missingAddressKeys[] = $addressKey;
            }
        }
        if (!empty($missingAddressKeys)) {
            printf(
                "Skipping addition of mailing address information because the "
                . "following required keys are missing: %s%s",
                json_encode($missingAddressKeys),
                PHP_EOL
            );
        } else {
            // Creates an OfflineUserAddressInfo object that contains all the required
            // elements of a mailing address.
            $addressIdentifier = new UserIdentifier(
                [
                'address_info' => new OfflineUserAddressInfo(
                    [
                    'hashed_first_name' => self::normalizeAndHash(
                        $rawRecord['firstName'],
                        false
                    ),
                    'hashed_last_name' => self::normalizeAndHash(
                        $rawRecord['lastName'],
                        false
                    ),
                    'country_code' => $rawRecord['countryCode'],
                    'postal_code' => $rawRecord['postalCode']
                    ]
                )
                ]
            );
            // Adds the address identifier to the user identifiers list.
            $userIdentifiers[] = $addressIdentifier;
        }
    }
    if (!empty($userIdentifiers)) {
        // Builds the UserData and adds it to the list.
        $userDataList[] = new UserData(['user_identifiers' => $userIdentifiers]);
    }
}

// Creates the operations to add users.
$operations = array_map(
    function (UserData $userData) {
        return new OfflineUserDataJobOperation(['create' => $userData]);
    },
    $userDataList
);
      

Python

def build_offline_user_data_job_operations(
    client: GoogleAdsClient,
) -> List[OfflineUserDataJobOperation]:
    """Creates a raw input list of unhashed user information.

    Each element of the list represents a single user and is a dict containing a
    separate entry for the keys "email", "phone", "first_name", "last_name",
    "country_code", and "postal_code". In your application, this data might come
    from a file or a database.

    Args:
        client: The Google Ads client.

    Returns:
        A list containing the operations.
    """
    # The first user data has an email address and a phone number.
    raw_record_1: Dict[str, str] = {
        "email": "dana@example.com",
        # Phone number to be converted to E.164 format, with a leading '+' as
        # required. This includes whitespace that will be removed later.
        "phone": "+1 800 5550101",
    }

    # The second user data has an email address, a mailing address, and a phone
    # number.
    raw_record_2: Dict[str, str] = {
        # Email address that includes a period (.) before the email domain.
        "email": "alex.2@example.com",
        # Address that includes all four required elements: first name, last
        # name, country code, and postal code.
        "first_name": "Alex",
        "last_name": "Quinn",
        "country_code": "US",
        "postal_code": "94045",
        # Phone number to be converted to E.164 format, with a leading '+' as
        # required.
        "phone": "+1 800 5550102",
    }

    # The third user data only has an email address.
    raw_record_3: Dict[str, str] = {"email": "charlie@example.com"}

    # Adds the raw records to a raw input list.
    raw_records: List[Dict[str, str]] = [
        raw_record_1,
        raw_record_2,
        raw_record_3,
    ]

    operations: List[OfflineUserDataJobOperation] = []
    # Iterates over the raw input list and creates a UserData object for each
    # record.
    for record in raw_records:
        # Creates a UserData object that represents a member of the user list.
        user_data: UserData = client.get_type("UserData")

        # Checks if the record has email, phone, or address information, and
        # adds a SEPARATE UserIdentifier object for each one found. For example,
        # a record with an email address and a phone number will result in a
        # UserData with two UserIdentifiers.

        # 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:

        # incorrect_user_identifier = client.get_type("UserIdentifier")
        # incorrect_user_identifier.hashed_email = "..."
        # incorrect_user_identifier.hashed_phone_number = "..."

        # The separate 'if' statements below demonstrate the correct approach
        # for creating a UserData object for a member with multiple
        # UserIdentifiers.

        # Checks if the record has an email address, and if so, adds a
        # UserIdentifier for it.
        if "email" in record:
            user_identifier: UserIdentifier = client.get_type("UserIdentifier")
            user_identifier.hashed_email = normalize_and_hash(
                record["email"], True
            )
            # Adds the hashed email identifier to the UserData object's list.
            user_data.user_identifiers.append(user_identifier)

        # Checks if the record has a phone number, and if so, adds a
        # UserIdentifier for it.
        if "phone" in record:
            user_identifier: UserIdentifier = client.get_type("UserIdentifier")
            user_identifier.hashed_phone_number = normalize_and_hash(
                record["phone"], True
            )
            # Adds the hashed phone number identifier to the UserData object's
            # list.
            user_data.user_identifiers.append(user_identifier)

        # Checks if the record has all the required mailing address elements,
        # and if so, adds a UserIdentifier for the mailing address.
        if "first_name" in record:
            required_keys = ("last_name", "country_code", "postal_code")
            # Checks if the record contains all the other required elements of
            # a mailing address.
            if not all(key in record for key in required_keys):
                # Determines which required elements are missing from the
                # record.
                missing_keys = record.keys() - required_keys
                print(
                    "Skipping addition of mailing address information "
                    "because the following required keys are missing: "
                    f"{missing_keys}"
                )
            else:
                user_identifier: UserIdentifier = client.get_type(
                    "UserIdentifier"
                )
                address_info: AddressInfo = user_identifier.address_info
                address_info.hashed_first_name = normalize_and_hash(
                    record["first_name"], False
                )
                address_info.hashed_last_name = normalize_and_hash(
                    record["last_name"], False
                )
                address_info.country_code = record["country_code"]
                address_info.postal_code = record["postal_code"]
                user_data.user_identifiers.append(user_identifier)

        # If the user_identifiers repeated field is not empty, create a new
        # OfflineUserDataJobOperation and add the UserData to it.
        if user_data.user_identifiers:
            operation: OfflineUserDataJobOperation = client.get_type(
                "OfflineUserDataJobOperation"
            )
            operation.create = user_data
            operations.append(operation)
      

Ruby

# Create a list of unhashed user data records that we will format in the
# following steps to prepare for the API.
raw_records = [
  # The first user data has an email address and a phone number.
  {
    email: 'dana@example.com',
    # Phone number to be converted to E.164 format, with a leading '+' as
    # required. This includes whitespace that will be removed later.
    phone: '+1 800 5550100',
  },
  # The second user data has an email address, a phone number, and an address.
  {
    # Email address that includes a period (.) before the Gmail domain.
    email: 'alex.2@example.com',
    # Address that includes all four required elements: first name, last
    # name, country code, and postal code.
    first_name: 'Alex',
    last_name: 'Quinn',
    country_code: 'US',
    postal_code: '94045',
    # Phone number to be converted to E.164 format, with a leading '+' as
    # required.
    phone: '+1 800 5550102',
  },
  # The third user data only has an email address.
  {
    email: 'charlie@example.com',
  },
]

# Create a UserData for each entry in the raw records.
user_data_list = raw_records.map do |record|
  client.resource.user_data do |data|
    if record[:email]
      data.user_identifiers << client.resource.user_identifier do |ui|
        ui.hashed_email = normalize_and_hash(record[:email], true)
      end
    end
    if record[:phone]
      data.user_identifiers << client.resource.user_identifier do |ui|
        ui.hashed_phone_number = normalize_and_hash(record[:phone], true)
      end
    end
    if record[:first_name]
      # Check that we have all the required information.
      missing_keys = [:last_name, :country_code, :postal_code].reject {|key|
        record[key].nil?
      }
      if missing_keys.empty?
        # If nothing is missing, add the address.
        data.user_identifiers << client.resource.user_identifier do |ui|
          ui.address_identifier = client.resource.offline_user_address_info do |address|
            address.hashed_first_name = normalize_and_hash(record[:first_name])
            address.hashed_last_name = normalize_and_hash(record[:last_name])
            address.country_code = record[:country_code]
            address.postal_code = record[:postal_code]
          end
        end
      else
        # If some data is missing, skip this entry.
        puts "Skipping addition of mailing information because the following keys are missing:" \
          "#{missing_keys}"
      end
    end
  end
end

operations = user_data_list.map do |user_data|
  client.operation.create_resource.offline_user_data_job(user_data)
end
      

Perl

  # The first user data has an email address and a phone number.
  my $raw_record_1 = {
    email => 'dana@example.com',
    # Phone number to be converted to E.164 format, with a leading '+' as
    # required. This includes whitespace that will be removed later.
    phone => '+1 800 5550101',
  };

  # The second user data has an email address, a mailing address, and a phone
  # number.
  my $raw_record_2 = {
    # Email address that includes a period (.) before the Gmail domain.
    email => 'alex.2@example.com',
    # Address that includes all four required elements: first name, last
    # name, country code, and postal code.
    firstName   => 'Alex',
    lastName    => 'Quinn',
    countryCode => 'US',
    postalCode  => '94045',
    # Phone number to be converted to E.164 format, with a leading '+' as
    # required.
    phone => '+1 800 5550102',
  };

  # The third user data only has an email address.
  my $raw_record_3 = {email => 'charlie@example.com',};

  my $raw_records = [$raw_record_1, $raw_record_2, $raw_record_3];

  my $operations = [];
  foreach my $record (@$raw_records) {
    # Check if the record has email, phone, or address information, and adds a
    # SEPARATE UserIdentifier object for each one found. For example, a record
    # with an email address and a phone number will result in a UserData with two
    # UserIdentifiers.
    #
    # 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::V25::Common::UserIdentifier->new({
    #   hashedEmail => '...',
    #   hashedPhoneNumber => '...',
    # });
    #
    # The separate 'if' statements below demonstrate the correct approach for creating a
    # UserData object for a member with multiple UserIdentifiers.

    my $user_identifiers = [];

    # Check if the record has an email address, and if so, add a UserIdentifier for it.
    if (defined $record->{email}) {
      # Add the hashed email identifier to the list of UserIdentifiers.
      push(
        @$user_identifiers,
        Google::Ads::GoogleAds::V25::Common::UserIdentifier->new({
            hashedEmail => normalize_and_hash($record->{email}, 1)}));
    }

    # Check if the record has a phone number, and if so, add a UserIdentifier for it.
    if (defined $record->{phone}) {
      # Add the hashed phone number identifier to the list of UserIdentifiers.
      push(
        @$user_identifiers,
        Google::Ads::GoogleAds::V25::Common::UserIdentifier->new({
            hashedPhoneNumber => normalize_and_hash($record->{phone}, 1)}));
    }

    # Check if the record has all the required mailing address elements, and if so, add
    # a UserIdentifier for the mailing address.
    if (defined $record->{firstName}) {
      my $required_keys = ["lastName", "countryCode", "postalCode"];
      my $missing_keys  = [];

      foreach my $key (@$required_keys) {
        if (!defined $record->{$key}) {
          push(@$missing_keys, $key);
        }
      }

      if (@$missing_keys) {
        print
"Skipping addition of mailing address information because the following"
          . "keys are missing: "
          . join(",", @$missing_keys);
      } else {
        push(
          @$user_identifiers,
          Google::Ads::GoogleAds::V25::Common::UserIdentifier->new({
              addressInfo =>
                Google::Ads::GoogleAds::V25::Common::OfflineUserAddressInfo->
                new({
                  # First and last name must be normalized and hashed.
                  hashedFirstName => normalize_and_hash($record->{firstName}),
                  hashedLastName  => normalize_and_hash($record->{lastName}),
                  # Country code and zip code are sent in plain text.
                  countryCode => $record->{countryCode},
                  postalCode  => $record->{postalCode},
                })}));
      }
    }

    # If the user_identifiers array is not empty, create a new
    # OfflineUserDataJobOperation and add the UserData to it.
    if (@$user_identifiers) {
      my $user_data = Google::Ads::GoogleAds::V25::Common::UserData->new({
          userIdentifiers => [$user_identifiers]});
      push(
        @$operations,
        Google::Ads::GoogleAds::V25::Services::OfflineUserDataJobService::OfflineUserDataJobOperation
          ->new({
            create => $user_data
          }));
    }
  }
      

curl

Шаг 3. Проверьте загрузку списка и коэффициент соответствия

Когда у OfflineUserDataJob появляется SUCCESS статус, расчетный процент совпадений становится доступен в поле operation_metadata.match_rate_range. Если вы запросите это поле до завершения задания, значение в нем может быть нулевым. Чтобы убедиться, что список готов к проверке и таргетингу, рекомендуем запросить статус задания. Процесс может занять от 10 минут до 24 часов.

Пример кода для проверки статуса задания

Java

private void checkJobStatus(
    GoogleAdsClient googleAdsClient, long customerId, String offlineUserDataJobResourceName) {
  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, "
                + "offline_user_data_job.customer_match_user_list_metadata.user_list "
                + "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,
          offlineUserDataJob.getCustomerMatchUserListMetadata().getUserList());
    } 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);
    }
  }
}

      

C#

private static void CheckJobStatusAndPrintResults(GoogleAdsClient client, long customerId,
    string offlineUserDataJobResourceName)
{
    // Get the GoogleAdsService.
    GoogleAdsServiceClient service = client.GetService(Services.V25.GoogleAdsService);

    string query = "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, " +
        "offline_user_data_job.customer_match_user_list_metadata.user_list " +
        "FROM offline_user_data_job WHERE " +
        $"offline_user_data_job.resource_name = '{offlineUserDataJobResourceName}'";

    // Issues the query and gets the GoogleAdsRow containing the job from the response.
    GoogleAdsRow googleAdsRow = service.Search(customerId.ToString(), query).First();

    OfflineUserDataJob offlineUserDataJob = googleAdsRow.OfflineUserDataJob;
    Console.WriteLine($"Offline user data job ID {offlineUserDataJob.Id} with type " +
        $"'{offlineUserDataJob.Type}' has status: {offlineUserDataJob.Status}");

    switch (offlineUserDataJob.Status)
    {
        case OfflineUserDataJobStatus.Success:
            // Prints information about the user list.
            PrintCustomerMatchUserListInfo(client, customerId,
                offlineUserDataJob.CustomerMatchUserListMetadata.UserList);
            break;

        case OfflineUserDataJobStatus.Failed:
            Console.WriteLine($"  Failure reason: {offlineUserDataJob.FailureReason}");
            break;

        case OfflineUserDataJobStatus.Pending:
        case OfflineUserDataJobStatus.Running:
            Console.WriteLine("To check the status of the job periodically, use the " +
                $"following GAQL query with GoogleAdsService.search:\n\n{query}");
            break;
    }
}
      

PHP

    private static function checkJobStatus(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $offlineUserDataJobResourceName
    ) {
        $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();

        // Creates a query that retrieves the offline user data job.
        $query = "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, "
              . "offline_user_data_job.customer_match_user_list_metadata.user_list "
              . "FROM offline_user_data_job "
              . "WHERE offline_user_data_job.resource_name = '$offlineUserDataJobResourceName'";

        // Issues a search request to get the GoogleAdsRow containing the job from the response.
        /**
 * @var GoogleAdsRow $googleAdsRow
*/
        $googleAdsRow =
            $googleAdsServiceClient->search(SearchGoogleAdsRequest::build($customerId, $query))
            ->getIterator()
            ->current();
        $offlineUserDataJob = $googleAdsRow->getOfflineUserDataJob();

        // Prints out some information about the offline user data job.
        $offlineUserDataJobStatus = $offlineUserDataJob->getStatus();
        printf(
            "Offline user data job ID %d with type '%s' has status: %s.%s",
            $offlineUserDataJob->getId(),
            OfflineUserDataJobType::name($offlineUserDataJob->getType()),
            OfflineUserDataJobStatus::name($offlineUserDataJobStatus),
            PHP_EOL
        );

        if ($offlineUserDataJobStatus === OfflineUserDataJobStatus::SUCCESS) {
            // Prints information about the user list.
            self::printCustomerMatchUserListInfo(
                $googleAdsClient,
                $customerId,
                $offlineUserDataJob->getCustomerMatchUserListMetadata()->getUserList()
            );
        } elseif ($offlineUserDataJobStatus === OfflineUserDataJobStatus::FAILED) {
            printf("  Failure reason: %s.%s", $offlineUserDataJob->getFailureReason(), PHP_EOL);
        } elseif (
            $offlineUserDataJobStatus === OfflineUserDataJobStatus::PENDING
            || $offlineUserDataJobStatus === OfflineUserDataJobStatus::RUNNING
        ) {
            printf(
                '%1$sTo check the status of the job periodically, use the following GAQL query with'
                . ' GoogleAdsService.search:%1$s%2$s%1$s',
                PHP_EOL,
                $query
            );
        }
    }
      

Python

def check_job_status(
    client: GoogleAdsClient,
    customer_id: str,
    offline_user_data_job_resource_name: str,
) -> None:
    """Retrieves, checks, and prints the status of the offline user data job.

    If the job is completed successfully, information about the user list is
    printed. Otherwise, a GAQL query will be printed, which can be used to
    check the job status at a later date.

    Offline user data jobs may take 6 hours or more to complete, so checking the
    status periodically, instead of waiting, can be more efficient.

    Args:
        client: The Google Ads client.
        customer_id: The ID for the customer that owns the user list.
        offline_user_data_job_resource_name: The resource name of the offline
            user data job to get the status of.
    """
    query: str = 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,
          offline_user_data_job.customer_match_user_list_metadata.user_list
        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: GoogleAdsServiceClient = client.get_service(
        "GoogleAdsService"
    )
    results: SearchGoogleAdsStreamResponse = google_ads_service.search(
        customer_id=customer_id, query=query
    )
    offline_user_data_job_result: OfflineUserDataJob = next(
        iter(results)
    ).offline_user_data_job
    status_name: str = offline_user_data_job_result.status.name
    user_list_resource_name: str = (
        offline_user_data_job_result.customer_match_user_list_metadata.user_list
    )

    print(
        f"Offline user data job ID '{offline_user_data_job_result.id}' with type "
        f"'{offline_user_data_job_result.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_result.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}"
        )
      

Ruby

def check_job_status(client, customer_id, offline_user_data_job)
  query = <<~QUERY
    SELECT
      offline_user_data_job.id,
      offline_user_data_job.status,
      offline_user_data_job.type,
      offline_user_data_job.failure_reason,
      offline_user_data_job.customer_match_user_list_metadata.user_list
    FROM
      offline_user_data_job
    WHERE
      offline_user_data_job.resource_name = '#{offline_user_data_job}'
  QUERY

  row = client.service.google_ads.search(
    customer_id: customer_id,
    query: query,
  ).first

  job = row.offline_user_data_job
  puts "Offline user data job ID #{job.id} with type '#{job.type}' has status: #{job.status}."

  case job.status
  when :SUCCESS
    print_customer_match_user_list(client, customer_id, job.customer_match_user_list_metadata.user_list)
  when :FAILED
    puts "  Failure reason: #{job.failure_reason}"
  else
    puts "  To check the status of the job periodically, use the following GAQL " \
      "query with GoogleAdsService.search:"
    puts query
  end
end
      

Perl

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

  my $search_query =
    "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, " .
    "offline_user_data_job.customer_match_user_list_metadata.user_list " .
    "FROM offline_user_data_job " .
    "WHERE offline_user_data_job.resource_name = " .
    "'$offline_user_data_job_resource_name' LIMIT 1";

  my $search_request =
    Google::Ads::GoogleAds::V25::Services::GoogleAdsService::SearchGoogleAdsRequest
    ->new({
      customerId => $customer_id,
      query      => $search_query
    });

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

  my $iterator = Google::Ads::GoogleAds::Utils::SearchGoogleAdsIterator->new({
    service => $google_ads_service,
    request => $search_request
  });

  # The results have exactly one row.
  my $google_ads_row        = $iterator->next;
  my $offline_user_data_job = $google_ads_row->{offlineUserDataJob};
  my $status                = $offline_user_data_job->{status};

  printf
    "Offline user data job ID %d with type %s has status: %s.\n",
    $offline_user_data_job->{id},
    $offline_user_data_job->{type},
    $status;

  if ($status eq SUCCESS) {
    print_customer_match_user_list_info($api_client, $customer_id,
      $offline_user_data_job->{customerMatchUserListMetadata}{userList});
  } elsif ($status eq FAILED) {
    print "Failure reason: $offline_user_data_job->{failureReason}";
  } elsif (grep /$status/, (PENDING, RUNNING)) {
    print
      "To check the status of the job periodically, use the following GAQL " .
      "query with the GoogleAdsService->search() method:\n$search_query\n";
  }

  return 1;
}
      

curl

Чтобы проверить размер списка, можно запросить ресурс user_list.

Пример кода для запроса ресурса user_list

Java

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

C#

 // Get the GoogleAdsService.
 GoogleAdsServiceClient service =
     client.GetService(Services.V25.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.");
        }
    }
);
      

PHP

        $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(
            SearchGoogleAdsStreamRequest::build($customerId, $query)
        );
      

Python

googleads_service_client: GoogleAdsServiceClient = client.get_service(
    "GoogleAdsService"
)

# Creates a query that retrieves the user list.
query: str = 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: SearchGoogleAdsStreamResponse = (
    googleads_service_client.search(customer_id=customer_id, query=query)
)
      

Ruby

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

Perl

# 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::V25::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
  });
      

curl

В целях конфиденциальности размер списка пользователей будет показываться как ноль, пока в нем не будет хотя бы 100 участников. После этого размер округляется до двух наиболее значимых цифр.

Ошибки, возникающие при выполнении OfflineUserDataJob, можно получить с помощью ресурса offline_user_data_job, используя язык запросов Google Рекламы. Однако в этом отчете нет информации о неудачных сопоставлениях, поскольку при сопоставлении сравниваются только хеши. Если у вас возникли проблемы со списками клиентов, ознакомьтесь с руководством по устранению неполадок.

Сравнение с интерфейсом Google Рекламы

В Менеджере аудиторий в интерфейсе Google Рекламы список может выглядеть меньше, чем ожидалось. В этом представлении показывается количество активных пользователей в списке. Дополнительную информацию вы найдете в руководстве по устранению неполадок.

Поскольку на заполнение списка участниками может уйти до 24 часов, при загрузке в список аудитории чаще, чем раз в 12 часов, в интерфейсе Google Рекламы может отображаться статус In Progress.

Шаг 4. Настройте таргетинг на список

Вы можете настроить таргетинг на список на уровне группы объявлений или кампании. Процесс аналогичен тому, как работают другие критерии таргетинга в API.

Пример кода для таргетинга объявлений в группе объявлений на список пользователей

Java

private String targetAdsInAdGroupToUserList(
    GoogleAdsClient googleAdsClient, long customerId, long adGroupId, String userList) {
  // Creates the ad group criterion targeting members of the user list.
  AdGroupCriterion adGroupCriterion =
      AdGroupCriterion.newBuilder()
          .setAdGroup(ResourceNames.adGroup(customerId, adGroupId))
          .setUserList(UserListInfo.newBuilder().setUserList(userList).build())
          .build();

  // Creates the operation.
  AdGroupCriterionOperation operation =
      AdGroupCriterionOperation.newBuilder().setCreate(adGroupCriterion).build();

  // Creates the ad group criterion service.
  try (AdGroupCriterionServiceClient adGroupCriterionServiceClient =
      googleAdsClient.getLatestVersion().createAdGroupCriterionServiceClient()) {
    // Adds the ad group criterion.
    MutateAdGroupCriteriaResponse response =
        adGroupCriterionServiceClient.mutateAdGroupCriteria(
            Long.toString(customerId), ImmutableList.of(operation));
    // Gets and prints the results.
    String adGroupCriterionResourceName = response.getResults(0).getResourceName();
    System.out.printf(
        "Successfully created ad group criterion with resource name '%s' "
            + "targeting user list with resource name '%s' with ad group with ID %d.%n",
        adGroupCriterionResourceName, userList, adGroupId);
    return adGroupCriterionResourceName;
  }
}

      

C#

private string TargetAdsInAdGroupToUserList(
    GoogleAdsClient client, long customerId, long adGroupId, string userListResourceName)
{
    // Get the AdGroupCriterionService client.
    AdGroupCriterionServiceClient adGroupCriterionServiceClient = client.GetService
        (Services.V25.AdGroupCriterionService);

    // Create the ad group criterion targeting members of the user list.
    AdGroupCriterion adGroupCriterion = new AdGroupCriterion
    {
        AdGroup = ResourceNames.AdGroup(customerId, adGroupId),
        UserList = new UserListInfo
        {
            UserList = userListResourceName
        }
    };

    // Create the operation.
    AdGroupCriterionOperation adGroupCriterionOperation = new AdGroupCriterionOperation
    {
        Create = adGroupCriterion
    };

    // Add the ad group criterion, then print and return the new criterion's resource name.
    MutateAdGroupCriteriaResponse mutateAdGroupCriteriaResponse =
        adGroupCriterionServiceClient.MutateAdGroupCriteria(customerId.ToString(),
            new[] { adGroupCriterionOperation });

    string adGroupCriterionResourceName =
        mutateAdGroupCriteriaResponse.Results.First().ResourceName;
    Console.WriteLine("Successfully created ad group criterion with resource name " +
        $"'{adGroupCriterionResourceName}' targeting user list with resource name " +
        $"'{userListResourceName}' with ad group with ID {adGroupId}.");
    return adGroupCriterionResourceName;
}
      

PHP

    private static function targetAdsInAdGroupToUserList(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        int $adGroupId,
        string $userListResourceName
    ): string {
        // Creates the ad group criterion targeting members of the user list.
        $adGroupCriterion = new AdGroupCriterion(
            [
            'ad_group' => ResourceNames::forAdGroup($customerId, $adGroupId),
            'user_list' => new UserListInfo(['user_list' => $userListResourceName])
            ]
        );

        // Creates the operation.
        $operation = new AdGroupCriterionOperation();
        $operation->setCreate($adGroupCriterion);

        // Issues a mutate request to add an ad group criterion.
        $adGroupCriterionServiceClient = $googleAdsClient->getAdGroupCriterionServiceClient();
        /**
 * @var MutateAdGroupCriteriaResponse $adGroupCriterionResponse
*/
        $adGroupCriterionResponse = $adGroupCriterionServiceClient->mutateAdGroupCriteria(
            MutateAdGroupCriteriaRequest::build($customerId, [$operation])
        );

        $adGroupCriterionResourceName =
            $adGroupCriterionResponse->getResults()[0]->getResourceName();
        printf(
            "Successfully created ad group criterion with resource name '%s' " .
            "targeting user list with resource name '%s' with ad group with ID %d.%s",
            $adGroupCriterionResourceName,
            $userListResourceName,
            $adGroupId,
            PHP_EOL
        );

        return $adGroupCriterionResourceName;
    }
      

Python

def target_ads_in_ad_group_to_user_list(
    client: GoogleAdsClient,
    customer_id: str,
    ad_group_id: str,
    user_list_resource_name: str,
) -> str:
    """Creates an ad group criterion that targets a user list with an ad group.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a str client customer ID used to create an ad group
            criterion.
        ad_group_id: a str ID for an ad group used to create an ad group
            criterion that targets members of a user list.
        user_list_resource_name: a str resource name for a user list.

    Returns:
        a str resource name for an ad group criterion.
    """
    ad_group_criterion_operation: AdGroupCriterionOperation = client.get_type(
        "AdGroupCriterionOperation"
    )
    # Creates the ad group criterion targeting members of the user list.
    ad_group_criterion: AdGroupCriterion = ad_group_criterion_operation.create
    ad_group_criterion.ad_group = client.get_service(
        "AdGroupService"
    ).ad_group_path(customer_id, ad_group_id)
    ad_group_criterion.user_list.user_list = user_list_resource_name

    ad_group_criterion_service: AdGroupCriterionServiceClient = (
        client.get_service("AdGroupCriterionService")
    )
    response: MutateAdGroupCriteriaResponse = (
        ad_group_criterion_service.mutate_ad_group_criteria(
            customer_id=customer_id, operations=[ad_group_criterion_operation]
        )
    )
    resource_name: str = response.results[0].resource_name
    print(
        "Successfully created ad group criterion with resource name: "
        f"'{resource_name}' targeting user list with resource name: "
        f"'{user_list_resource_name}' and with ad group with ID "
        f"{ad_group_id}."
    )
    return resource_name
      

Ruby

def target_ads_in_ad_group_to_user_list(
  client,
  customer_id,
  ad_group_id,
  user_list
)
  # Creates the ad group criterion targeting members of the user list.
  operation = client.operation.create_resource.ad_group_criterion do |agc|
    agc.ad_group = client.path.ad_group(customer_id, ad_group_id)
    agc.user_list = client.resource.user_list_info do |info|
      info.user_list = user_list
    end
  end

  # Issues a mutate request to create the ad group criterion.
  response = client.service.ad_group_criterion.mutate_ad_group_criteria(
    customer_id: customer_id,
    operations: [operation],
  )
  ad_group_criterion_resource_name = response.results.first.resource_name
  puts "Successfully created ad group criterion with resource name " \
    "'#{ad_group_criterion_resource_name}' targeting user list with resource name " \
    "'#{user_list}' with ad group with ID #{ad_group_id}"

  ad_group_criterion_resource_name
end
      

Perl

sub target_ads_in_ad_group_to_user_list {
  my ($api_client, $customer_id, $ad_group_id, $user_list_resource_name) = @_;

  # Create the ad group criterion targeting members of the user list.
  my $ad_group_criterion =
    Google::Ads::GoogleAds::V25::Resources::AdGroupCriterion->new({
      adGroup => Google::Ads::GoogleAds::V25::Utils::ResourceNames::ad_group(
        $customer_id, $ad_group_id
      ),
      userList => Google::Ads::GoogleAds::V25::Common::UserListInfo->new({
          userList => $user_list_resource_name
        })});

  # Create the operation.
  my $ad_group_criterion_operation =
    Google::Ads::GoogleAds::V25::Services::AdGroupCriterionService::AdGroupCriterionOperation
    ->new({
      create => $ad_group_criterion
    });

  # Add the ad group criterion, then print and return the new criterion's resource name.
  my $ad_group_criteria_response =
    $api_client->AdGroupCriterionService()->mutate({
      customerId => $customer_id,
      operations => [$ad_group_criterion_operation]});

  my $ad_group_criterion_resource_name =
    $ad_group_criteria_response->{results}[0]{resourceName};
  printf "Successfully created ad group criterion with resource name '%s' " .
    "targeting user list with resource name '%s' with ad group with ID %d.\n",
    $ad_group_criterion_resource_name, $user_list_resource_name, $ad_group_id;

  return $ad_group_criterion_resource_name;
}
      

curl

Как настроить таргетинг на несколько списков клиентов

crm_based_user_list можно сочетать с другим crm_based_user_list только при использовании logical_user_list. Все правила для crm_based_user_list применяются к полученному списку пользователей.

Как управлять списками и обновлять их

После создания списков клиентов вам, скорее всего, потребуется периодически обновлять их, чтобы отражать изменения в клиентской базе. С помощью Google Ads API можно управлять существующими списками, добавляя и удаляя пользователей.

Основные операции по обновлению списков:

  • Добавить новых пользователей – добавить данные клиентов в существующий список.
  • Удалить отдельных пользователей из списка на основе их идентификаторов.
  • Заменить всех участников. Очистите существующий список и полностью замените его содержимое. Рекомендуется добавлять и удалять участников.

Подробнее об управлении списками клиентов…