Stay organized with collectionsSave and categorize content based on your preferences.
Reference this guide for customer list updates, removals, and reporting.
Update a list with OfflineUserDataJobService
Once you've created your customer lists and set up targeting, it's best to
refresh them regularly.
To update your lists with the latest data, it is generally more efficient to
append or remove individual users, rather than removing all users from the list
and uploading them from scratch.
Append to a list
To append to an existing list, create anOfflineUserDataJobin the same way
you would when creating a new customer list.
defadd_users_to_customer_match_user_list(client:GoogleAdsClient,customer_id:str,user_list_resource_name:str,run_job:bool,offline_user_data_job_id:Optional[str],ad_user_data_consent:Optional[str],ad_personalization_consent:Optional[str],)->None:"""Uses Customer Match to create and add users to a new user list.Args:client: The Google Ads client.customer_id: The ID for the customer that owns the user list.user_list_resource_name: The resource name of the user list to which toadd users.run_job: If true, runs the OfflineUserDataJob after adding operations.Otherwise, only adds operations to the job.offline_user_data_job_id: ID of an existing OfflineUserDataJob in thePENDING state. If None, a new job is created.ad_user_data_consent: The consent status for ad user data for allmembers in the job.ad_personalization_consent: The personalization consent status for aduser data for all members in the job."""# Creates the OfflineUserDataJobService client.offline_user_data_job_service_client:OfflineUserDataJobServiceClient=(client.get_service("OfflineUserDataJobService"))offline_user_data_job_resource_name:strifoffline_user_data_job_id:# Reuses the specified offline user data job.offline_user_data_job_resource_name=(offline_user_data_job_service_client.offline_user_data_job_path(customer_id,offline_user_data_job_id))else:# Creates a new offline user data job.offline_user_data_job:OfflineUserDataJob=client.get_type("OfflineUserDataJob")offline_user_data_job.type_=(client.enums.OfflineUserDataJobTypeEnum.CUSTOMER_MATCH_USER_LIST)offline_user_data_job.customer_match_user_list_metadata.user_list=(user_list_resource_name)# Specifies whether user consent was obtained for the data you are# uploading. For more details, see:# https://www.google.com/about/company/user-consent-policyifad_user_data_consent:offline_user_data_job.customer_match_user_list_metadata.consent.ad_user_data=client.enums.ConsentStatusEnum[ad_user_data_consent]ifad_personalization_consent:offline_user_data_job.customer_match_user_list_metadata.consent.ad_personalization=client.enums.ConsentStatusEnum[ad_personalization_consent]# Issues a request to create an offline user data job.create_offline_user_data_job_response:(CreateOfflineUserDataJobResponse)=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}'.")# Issues a request to add the operations to the offline user data job.# Best Practice: This example only adds a few operations, so it only sends# one AddOfflineUserDataJobOperations request. If your application is adding# a large number of operations, split the operations into batches and send# multiple AddOfflineUserDataJobOperations requests for the SAME job. See# https://developers.google.com/google-ads/api/docs/remarketing/audience-types/customer-match#customer_match_considerations# and https://developers.google.com/google-ads/api/docs/best-practices/quotas#user_data# for more information on the per-request limits.request:AddOfflineUserDataJobOperationsRequest=client.get_type("AddOfflineUserDataJobOperationsRequest")request.resource_name=offline_user_data_job_resource_namerequest.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:AddOfflineUserDataJobOperationsResponse=(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:Union[status_pb2.Status,None]=getattr(response,"partial_failure_error",None)ifgetattr(partial_failure,"code",None)!=0:error_details:Iterable[Any,None]=getattr(partial_failure,"details",[])forerror_detailinerror_details:failure_message:GoogleAdsFailure=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:GoogleAdsFailure=type(failure_message).deserialize(error_detail.value)errors:Iterable[GoogleAdsError]=failure_object.errorsforerrorinerrors: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.")ifnotrun_job:print("Not running offline user data job "f"'{offline_user_data_job_resource_name}', as requested.")return# Issues a request to run the offline user data job for executing all# added operations.offline_user_data_job_service_client.run_offline_user_data_job(resource_name=offline_user_data_job_resource_name)# Retrieves and displays the job status.check_job_status(client,customer_id,offline_user_data_job_resource_name)
defadd_users_to_customer_match_user_list(client,customer_id,run_job,user_list,job_id,ad_user_data_consent,ad_personalization_consent)offline_user_data_service=client.service.offline_user_data_jobjob_name=ifjob_id.nil?# Creates the offline user data job.offline_user_data_job=client.resource.offline_user_data_jobdo|job|job.type=:CUSTOMER_MATCH_USER_LISTjob.customer_match_user_list_metadata=client.resource.customer_match_user_list_metadatado|m|m.user_list=user_listif!ad_user_data_consent.nil?||!ad_personalization_consent.nil?m.consent=client.resource.consentdo|c|# Specifies whether user consent was obtained for the data you are# uploading. For more details, see:# https://www.google.com/about/company/user-consent-policyunlessad_user_data_consent.nil?c.ad_user_data=ad_user_data_consentendunlessad_personalization_consent.nil?c.ad_personalization=ad_personalization_consentendendendendend# 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_nameputs"Created an offline user data job with resource name: "\"#{offline_user_data_job_resource_name}"offline_user_data_job_resource_nameelseclient.path.offline_user_data_job(customer_id,job_id)end# Issues a request to add the operations to the offline user data job. This# example only adds a few operations, so it only sends one# AddOfflineUserDataJobOperations request. If your application is adding a# large number of operations, split the operations into batches and send# multiple AddOfflineUserDataJobOperations requests for the SAME job. See# https://developers.google.com/google-ads/api/docs/remarketing/audience-types/customer-match#customer_match_considerations# and https://developers.google.com/google-ads/api/docs/best-practices/quotas#user_data# for more information on the per-request limits.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.ifresponse.partial_failure_errorfailures=client.decode_partial_failure_error(response.partial_failure_error)failures.eachdo|failure|failure.errors.eachdo|error|human_readable_error_path=error.location.field_path_elements.map{|location_info|iflocation_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}"putserrmsgendendendputs"The operations are added to the offline user data job."unlessrun_jobputs"Not running offline user data job#{job_name}, as requested."returnend# 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."# Offline user data jobs may take 6 hours or more 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.check_job_status(client,customer_id,offline_user_data_job_resource_name,)end
subadd_users_to_customer_match_user_list{my($api_client,$customer_id,$run_job,$user_list_resource_name,$offline_user_data_job_id,$ad_personalization_consent,$ad_user_data_consent)=@_;my$offline_user_data_job_service=$api_client->OfflineUserDataJobService();my$offline_user_data_job_resource_name=undef;if(!defined$offline_user_data_job_id){# Create a new offline user data job.my$offline_user_data_job=Google::Ads::GoogleAds::V21::Resources::OfflineUserDataJob->new({type=>CUSTOMER_MATCH_USER_LIST,customerMatchUserListMetadata=>Google::Ads::GoogleAds::V21::Common::CustomerMatchUserListMetadata->new({userList=>$user_list_resource_name})});# Add consent information to the job if specified.if($ad_personalization_consentor$ad_user_data_consent){my$consent=Google::Ads::GoogleAds::V21::Common::Consent->new({});if($ad_personalization_consent){$consent->{adPersonalization}=$ad_personalization_consent;}if($ad_user_data_consent){$consent->{adUserData}=$ad_user_data_consent;}# Specify whether user consent was obtained for the data you are uploading.# See https://www.google.com/about/company/user-consent-policy for details.$offline_user_data_job->{customerMatchUserListMetadata}{consent}=$consent;}# 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});$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;}else{# Reuse the specified offline user data job.$offline_user_data_job_resource_name=Google::Ads::GoogleAds::V21::Utils::ResourceNames::offline_user_data_job($customer_id,$offline_user_data_job_id);}# Issue a request to add the operations to the offline user data job.# This example only adds a few operations, so it only sends one AddOfflineUserDataJobOperations# request. If your application is adding a large number of operations, split# the operations into batches and send multiple AddOfflineUserDataJobOperations# requests for the SAME job. See# https://developers.google.com/google-ads/api/docs/remarketing/audience-types/customer-match#customer_match_considerations# and https://developers.google.com/google-ads/api/docs/best-practices/quotas#user_data# for more information on the per-request limits.my$user_data_job_operations=build_offline_user_data_job_operations();my$response=$offline_user_data_job_service->add_operations({resourceName=>$offline_user_data_job_resource_name,enablePartialFailure=>"true",operations=>$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"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",scalar@{$partial_failure->{errors}},scalar@$user_data_job_operations,$response->{partialFailureError}{message};}else{printf"Successfully added %d operations to the offline user data job.\n",scalar@$user_data_job_operations;}if(!defined$run_job){print"Not running offline user data job $offline_user_data_job_resource_name, as requested.\n";return;}# 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});# Offline user data jobs may take 6 hours or more to complete, so instead of waiting# for the job to complete, this example retrieves and displays the job status once.# If the job is completed successfully, it prints information about the user list.# Otherwise, it prints, the query to use to check the job status again later.check_job_status($api_client,$customer_id,$offline_user_data_job_resource_name);}
Add one or moreUserIdentifierobjects to
theuser_identifiers[]repeated
field. A single user identifier can be used to remove a user from a list, even
if more than one identifiers were submitted that match the user.
Note that when aremove_alloperation is included, it must be the first
operation in a job. If not, then running the job returns anINVALID_OPERATION_ORDERerror. To completely replace the members of a user list with new members, order
the operations inAddOfflineUserDataJobOperationsRequestin this sequence:
For each new member, add acreateoperation, setting theirUserDatain
anOfflineUserDataJobOperation.
remove_alloperations are executed hourly, and could run for up to 24 hours.
Customer list refresh recommendations
You can retrieve recommendations of typeREFRESH_CUSTOMER_MATCH_LISTto identify customer lists which haven't been updated in some time. This is
especially useful if you're a third-party advertiser that enables its users
to manage customer lists.
TheUserDataServicehas a limit of 10
operations and 100 totaluser_identifiersper request, so it is a better fit
for small updates. For example, if each of yourUserDataobjects has oneUserIdentifierforhashed_emailand anotherUserIdentifierforhashed_phone_number, then your request can contain at most 50UserDataobjects.
TheUserDataServicecontains theUploadUserDatamethod, which accepts anUploadUserDataRequest. In addition to
thecustomer_id, theUploadUserDataRequestaccepts a list of operations
to create contacts and a required field calledcustomer_match_user_list_metadata, which is populated with the resource name
of the remarketing list to target.
Begin by creating anUploadUserDataRequestinstance in which you populate
thecustomer_idandcustomer_match_user_list_metadata:
Java
// Creates a request to add user data operations to the user list based on email addresses.StringuserListResourceName=ResourceNames.userList(customerId,userListId);UploadUserDataRequest.BuilderuploadUserDataRequest=UploadUserDataRequest.newBuilder().setCustomerId(String.valueOf(customerId)).setCustomerMatchUserListMetadata(CustomerMatchUserListMetadata.newBuilder().setUserList(StringValue.of(userListResourceName)).build());
To upload user contact information, setCrmBasedUserListInfo.upload_key_typetoCONTACT_INFO.
First, add the operations to theUploadUserDataRequestobject. Each operation
contains acreatefield populated withUserDataobjects that hold one or moreUserIdentifierinstances. EachUserIdentifiercontains one piece of identifying information
or can be one of several different types, each of which is described below.
To upload customer email addresses, create a newUserDataOperationand populate its create
field with aUserDataobject. The UserData object accepts a list ofuser_identifiers. Populate thehashed_emailfield with the customer email
addresses.
Java
ImmutableList<String>EMAILS=ImmutableList.of("client1@example.com","client2@example.com"," Client3@example.com ");// Hash normalized email addresses based on SHA-256 hashing algorithm.List<UserDataOperation>userDataOperations=newArrayList<>(EMAILS.size());for(Stringemail:EMAILS){UserDataOperationuserDataOperation=UserDataOperation.newBuilder().setCreate(UserData.newBuilder().addUserIdentifiers(UserIdentifier.newBuilder().setHashedEmail(StringValue.of(toSHA256String(email))).build()).build()).build();userDataOperations.add(userDataOperation);}uploadUserDataRequest.addAllOperations(userDataOperations);
Uploading useraddress_infois similar to uploading user email addresses.
However, instead of passing ahashed_email, populate theaddress_infofield
with anOfflineUserAddressInfoobject
containing the user'sfirst_name,last_name,country_code, andpostal_code. Like email addresses,first_nameandlast_nameare considered
personally identifiable information and must be hashed before uploading.
Java
StringfirstName="Alex";StringlastName="Quinn";StringcountryCode="US";StringpostalCode="94045";UserIdentifieruserIdentifierWithAddress=UserIdentifier.newBuilder().setAddressInfo(OfflineUserAddressInfo.newBuilder()// First and last name must be normalized and hashed..setHashedFirstName(StringValue.of(toSHA256String(firstName))).setHashedLastName(StringValue.of(toSHA256String(lastName)))// Country code and zip code are sent in plaintext..setCountryCode(StringValue.of(countryCode)).setPostalCode(StringValue.of(postalCode)).build()).build();UserDataOperationuserDataOperation=UserDataOperation.newBuilder().setCreate(UserData.newBuilder().addUserIdentifiers(userIdentifierWithAddress).build()).build();uploadUserDataRequest.addOperations(userDataOperation);
After adding operations to theUploadUserDataRequestinstance, call theuploadUserDatamethod on theUserDataServiceClientto send the request to
the Google Ads API server. You can see if the request was successful by getting the
operations count and upload time in the response object. Keep in mind that it
may take several hours for the list to be populated with members.
Java
// Creates the user data service client.try(UserDataServiceClientuserDataServiceClient=googleAdsClient.getLatestVersion().createUserDataServiceClient()){// Add operations to the user list based on the user data type.UploadUserDataResponseresponse=userDataServiceClient.uploadUserData(uploadUserDataRequest.build());// Displays the results.// Reminder: it may take several hours for the list to be populated with members.System.out.printf("Received %d operations at %s",response.getReceivedOperationsCount().getValue(),response.getUploadDateTime().getValue());}
Switch list targeting level
If you need to switch the level at which your list is targeting, for example from
ad group to campaign level targeting, consult the switching targeting levelsguide.
Review list performance
In order to collect performance data for your audience segments, issue a search
request against thead_group_audience_viewor thecampaign_audience_viewresource.
For example, you might look at theconversionsorcost_per_conversionto
determine if targeting the audience segment is actually leading to more
conversions, then adjust your bid modifiers accordingly.
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2025-09-03 UTC."],[[["\u003cp\u003eLeverage the OfflineUserDataJob to efficiently add or remove users from existing Customer Match lists in Google Ads.\u003c/p\u003e\n"],["\u003cp\u003eFor large updates, use batching and separate jobs for adding and removing users to enhance efficiency and avoid errors.\u003c/p\u003e\n"],["\u003cp\u003eSecurely manage user data by hashing sensitive information like email addresses and names before uploading to Google Ads.\u003c/p\u003e\n"],["\u003cp\u003eOptimize Customer Match list management by using membership lifespan for expirations and Customer Match List Refresh recommendations.\u003c/p\u003e\n"],["\u003cp\u003eAnalyze ad group and campaign audience views to evaluate list performance and ensure effective targeting for optimal campaign outcomes.\u003c/p\u003e\n"]]],[],null,[]]