1st party data is the new gold. How do you upload to Google Ads to activate them?
There are many reasons why you should upload your audiences to Google ads.
Some benefits:
- Reach the Right People: By uploading your customer data, you can create highly targeted campaigns that reach people who have already shown interest in your products or services.
- Tailored Messaging: You can create custom ads and landing pages that resonate with specific audience segments based on their demographics, interests, or past interactions with your brand. This leads to a more personalized experience, boosting engagement and conversions.
- Lookalike Audience: Create lookalike audiences based on your existing users. This help you to reach new high value users.
- Improve Bidding Signals: Customer Match lists provide valuable first-party data about your existing customers, including their demographics, purchase history, and interests. This information can be used by Smart Bidding algorithms to better understand user behavior and predict the likelihood of conversion.
Once we understand that there are a lot of benefits in uploading your existing Audience lists, we can look at how we could do that.
The easiest way is to go to Google Ads UI and just upload it or schedule it to fetch it from your server via SFTP. BUT what if you want to be fancy and upload via Google Ads API? Let’s go and see how we make that happened.
Requirement
Make sure you got this done:
- Google Ads API Config File. You can check out this guide of mine if you haven't set it up.
- Customer PII List or GCLID List
- A development environment for API calls (using a client library or REST). For this guide, we will use Python as the scripting language.
Let’s Go
Set up the environment
- Install the environment library. For this guide, we use python. So install Google Ads Python library.
pip install google-ads
- Open up google-ads-python/google-ads.yaml
Fill up the config file(Google-ads.yaml). If you need help, look at this guide.
- Import required library
import hashlib from google.ads.googleads.client import GoogleAdsClient from google.ads.googleads.errors import GoogleAdsException
Create an Empty Customer Match List
- Create a function to create an empty Customer Match List in Google Ads
def create_customer_match_user_list(client, customer_id): """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 = client.get_service("UserListService") # Creates the user list operation. user_list_operation = client.get_type("UserListOperation") # Creates the new user list. user_list = user_list_operation.create user_list.name = f"Customer Match list #{uuid.uuid4()}" user_list.description = ( "A list of customers that originated from email and physical addresses" ) # 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 ) # Customer Match user lists can set an unlimited membership life span; # to do so, use the special life span value 10000. Otherwise, membership # life span must be between 0 and 540 days inclusive. See: # https://developers.devsite.corp.google.com/google-ads/api/reference/rpc/latest/UserList#membership_life_span # Sets the membership life span to 30 days. user_list.membership_life_span = 30 response = user_list_service_client.mutate_user_lists( customer_id=customer_id, operations=[user_list_operation] ) user_list_resource_name = response.results[0].resource_name print( f"User list with resource name '{user_list_resource_name}' was created." ) return user_list_resource_name
Prepare your Data
Google will require email/phone number/address to be hashed in SHA256. We will focus just on email today.
Create a function to normalize(remove white space & lower case) and hash them in SHA256
def normalize_and_hash(s, remove_all_whitespace): """Normalizes and hashes a string with SHA-256. Args: s: The string to perform this operation on. remove_all_whitespace: If true, removes leading, trailing, and intermediate spaces from the string before hashing. If false, only removes leading and trailing spaces from the string before hashing. Returns: A normalized (lowercase, remove whitespace) and SHA-256 hashed string. """ # Normalizes by first converting all characters to lowercase, then trimming # spaces. if remove_all_whitespace: # Removes leading, trailing, and intermediate whitespace. s = "".join(s.split()) else: # Removes only leading and trailing spaces. s = s.strip().lower() # Hashes the normalized string using the hashing algorithm. return hashlib.sha256(s.encode()).hexdigest()
Upload List to job operation
Build a function to add email list into a job operation
def build_offline_user_data_job_operations(client, emailList): operations = [] for email in emailList: # Creates a UserData object that represents a member of the user list. user_data = client.get_type("UserData") user_identifier = 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) # 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 = client.get_type("OfflineUserDataJobOperation") operation.create = user_data operations.append(operation) return operations;
Add job operation into an upload request
def add_users_to_customer_match_user_list( client, customer_id, user_list_resource_name, run_job, offline_user_data_job_id, ad_user_data_consent, ad_personalization_consent, emailList ): """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 to add 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 the PENDING state. If None, a new job is created. ad_user_data_consent: The consent status for ad user data for all members in the job. ad_personalization_consent: The personalization consent status for ad user data for all members in the job. """ # Creates the OfflineUserDataJobService client. offline_user_data_job_service_client = client.get_service( "OfflineUserDataJobService" ) if offline_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 = 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-policy if ad_user_data_consent: offline_user_data_job.customer_match_user_list_metadata.consent.ad_user_data = client.enums.ConsentStatusEnum[ ad_user_data_consent ] if ad_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 = ( 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 = client.get_type("AddOfflineUserDataJobOperationsRequest") request.resource_name = offline_user_data_job_resource_name request.operations = build_offline_user_data_job_operations(client, emailList) request.enable_partial_failure = True # Issues a request to add the operations to the offline user data job. response = offline_user_data_job_service_client.add_offline_user_data_job_operations( request=request ) # Prints the status message if any partial failure error is returned. # Note: the details of each partial failure error are not printed here. # Refer to the error_handling/handle_partial_failure.py example to learn # more. # Extracts the partial failure from the response status. partial_failure = getattr(response, "partial_failure_error", None) if getattr(partial_failure, "code", None) != 0: error_details = getattr(partial_failure, "details", []) for error_detail in error_details: failure_message = client.get_type("GoogleAdsFailure") # Retrieve the class definition of the GoogleAdsFailure instance # in order to use the "deserialize" class method to parse the # error_detail string into a protobuf message object. failure_object = type(failure_message).deserialize( error_detail.value ) for error in failure_object.errors: print( "A partial failure at index " f"{error.location.field_path_elements[0].index} occurred.\n" f"Error message: {error.message}\n" f"Error code: {error.error_code}" ) print("The operations are added to the offline user data job.") if not run_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 )
Last step: Tie it all in
Setup a main function to run your script.
# GoogleAdsClient will read the google-ads.yaml configuration file in the # home directory if none is specified. googleads_client = GoogleAdsClient.load_from_storage(version="v16") if __name__ == "__main__": try: main( googleads_client, customer_id, run_job, user_list_id, offline_user_data_job_id, ad_user_data_consent, ad_personalization_consent, emailList ) except GoogleAdsException as ex: print( f"Request with ID '{ex.request_id}' failed with status " f"'{ex.error.code().name}' and includes the following errors:" ) for error in ex.failure.errors: print(f"\tError with message '{error.message}'.") if error.location: for field_path_element in error.location.field_path_elements: print(f"\t\tOn field: {field_path_element.field_name}") sys.exit(1) def main( client, customer_id, run_job, user_list_id, offline_user_data_job_id, ad_user_data_consent, ad_personalization_consent, emailList ): """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. run_job: if True, runs the OfflineUserDataJob after adding operations. Otherwise, only adds operations to the job. user_list_id: ID of an existing user list. If None, a new user list is created. offline_user_data_job_id: ID of an existing OfflineUserDataJob in the PENDING state. If None, a new job is created. ad_user_data_consent: The consent status for ad user data for all members in the job. ad_personalization_consent: The personalization consent status for ad user data for all members in the job. """ googleads_service = client.get_service("GoogleAdsService") if not offline_user_data_job_id: if user_list_id: # Uses the specified Customer Match user list. user_list_resource_name = googleads_service.user_list_path( customer_id, user_list_id ) else: # Creates a Customer Match user list. user_list_resource_name = create_customer_match_user_list( client, customer_id ) add_users_to_customer_match_user_list( client, customer_id, user_list_resource_name, run_job, offline_user_data_job_id, ad_user_data_consent, ad_personalization_consent, emailList )
All good? Connect with me at https://www.linkedin.com/in/joseph-sian-gou-wei/
A shameless plug here. I am an advertising professional with a mission to teach others about marketing technology. I’m putting Martech tutorials without a paywall.
If you enjoyed this article, please do consider to clap/follow me, or buy me a coffee here!
Cheers friends!