Upload Enhanced Conversions for Leads

Java

 // Copyright 2021 Google LLC 
 // 
 // Licensed under the Apache License, Version 2.0 (the "License"); 
 // you may not use this file except in compliance with the License. 
 // You may obtain a copy of the License at 
 // 
 //     https://www.apache.org/licenses/LICENSE-2.0 
 // 
 // Unless required by applicable law or agreed to in writing, software 
 // distributed under the License is distributed on an "AS IS" BASIS, 
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 // See the License for the specific language governing permissions and 
 // limitations under the License. 
 package 
  
 com.google.ads.googleads.examples.remarketing 
 ; 
 import 
  
 com.beust.jcommander.Parameter 
 ; 
 import 
  
 com.google.ads.googleads.examples.utils.ArgumentNames 
 ; 
 import 
  
 com.google.ads.googleads.examples.utils.CodeSampleParams 
 ; 
 import 
  
 com.google.ads.googleads.lib.GoogleAdsClient 
 ; 
 import 
  
 com.google.ads.googleads.v21.common.Consent 
 ; 
 import 
  
 com.google.ads.googleads.v21.common.UserIdentifier 
 ; 
 import 
  
 com.google.ads.googleads.v21.enums.ConsentStatusEnum.ConsentStatus 
 ; 
 import 
  
 com.google.ads.googleads.v21.enums.UserIdentifierSourceEnum.UserIdentifierSource 
 ; 
 import 
  
 com.google.ads.googleads.v21.errors.GoogleAdsError 
 ; 
 import 
  
 com.google.ads.googleads.v21.errors.GoogleAdsException 
 ; 
 import 
  
 com.google.ads.googleads.v21.services.ClickConversion 
 ; 
 import 
  
 com.google.ads.googleads.v21.services.ClickConversionResult 
 ; 
 import 
  
 com.google.ads.googleads.v21.services.ConversionUploadServiceClient 
 ; 
 import 
  
 com.google.ads.googleads.v21.services.SessionAttributeKeyValuePair 
 ; 
 import 
  
 com.google.ads.googleads.v21.services.SessionAttributesKeyValuePairs 
 ; 
 import 
  
 com.google.ads.googleads.v21.services.UploadClickConversionsRequest 
 ; 
 import 
  
 com.google.ads.googleads.v21.services.UploadClickConversionsResponse 
 ; 
 import 
  
 com.google.ads.googleads.v21.utils.ResourceNames 
 ; 
 import 
  
 com.google.common.collect.ImmutableMap 
 ; 
 import 
  
 com.google.protobuf.ByteString 
 ; 
 import 
  
 java.io.FileNotFoundException 
 ; 
 import 
  
 java.io.IOException 
 ; 
 import 
  
 java.io.UnsupportedEncodingException 
 ; 
 import 
  
 java.security.MessageDigest 
 ; 
 import 
  
 java.security.NoSuchAlgorithmException 
 ; 
 import 
  
 java.util.ArrayList 
 ; 
 import 
  
 java.util.Arrays 
 ; 
 import 
  
 java.util.List 
 ; 
 import 
  
 java.util.Map 
 ; 
 import 
  
 java.util.stream.Collectors 
 ; 
 /** 
 * Uploads an enhanced conversion for leads by uploading a ClickConversion with hashed, first-party 
 * user-provided data from your website lead forms. This includes user identifiers, and optionally a 
 * click ID and order ID. With this information, Google can tie the conversion to the ad that drove 
 * the lead. 
 */ 
 public 
  
 class 
 UploadEnhancedConversionsForLeads 
  
 { 
  
 private 
  
 static 
  
 class 
 UploadEnhancedConversionsForLeadsParams 
  
 extends 
  
 CodeSampleParams 
  
 { 
  
 @Parameter 
 ( 
 names 
  
 = 
  
 ArgumentNames 
 . 
 CUSTOMER_ID 
 , 
  
 required 
  
 = 
  
 true 
 ) 
  
 private 
  
 long 
  
 customerId 
 ; 
  
 @Parameter 
 ( 
 names 
  
 = 
  
 ArgumentNames 
 . 
 CONVERSION_ACTION_ID 
 , 
  
 required 
  
 = 
  
 true 
 ) 
  
 private 
  
 long 
  
 conversionActionId 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 ArgumentNames 
 . 
 CONVERSION_DATE_TIME 
 , 
  
 required 
  
 = 
  
 true 
 , 
  
 description 
  
 = 
  
 "The date time at which the conversion occurred. " 
  
 + 
  
 "Must be after the click time, and must include the time zone offset. " 
  
 + 
  
 "The format is 'yyyy-mm-dd hh:mm:ss+|-hh:mm', e.g. '2019-01-01 12:32:45-08:00'." 
 ) 
  
 private 
  
 String 
  
 conversionDateTime 
 ; 
  
 @Parameter 
 ( 
 names 
  
 = 
  
 ArgumentNames 
 . 
 CONVERSION_VALUE 
 , 
  
 required 
  
 = 
  
 true 
 ) 
  
 private 
  
 Double 
  
 conversionValue 
 ; 
  
 @Parameter 
 ( 
 names 
  
 = 
  
 ArgumentNames 
 . 
 ORDER_ID 
 , 
  
 required 
  
 = 
  
 false 
 ) 
  
 private 
  
 String 
  
 orderId 
 ; 
  
 @Parameter 
 ( 
 names 
  
 = 
  
 ArgumentNames 
 . 
 GCLID 
 , 
  
 required 
  
 = 
  
 false 
 ) 
  
 private 
  
 String 
  
 gclid 
 ; 
  
 @Parameter 
 ( 
 names 
  
 = 
  
 ArgumentNames 
 . 
 AD_USER_DATA_CONSENT 
 , 
  
 required 
  
 = 
  
 false 
 ) 
  
 private 
  
 ConsentStatus 
  
 adUserDataConsent 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 ArgumentNames 
 . 
 SESSION_ATTRIBUTES_ENCODED 
 , 
  
 required 
  
 = 
  
 false 
 , 
  
 description 
  
 = 
  
 "A session attributes token. Only one of sessionAttributesEncoded or sessionAttributesMap" 
  
 + 
  
 " should be passed." 
 ) 
  
 private 
  
 String 
  
 sessionAttributesEncoded 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 ArgumentNames 
 . 
 SESSION_ATTRIBUTES_MAP 
 , 
  
 required 
  
 = 
  
 false 
 , 
  
 description 
  
 = 
  
 "A " 
  
 + 
  
 "space-delimited list of session attribute key value pairs. Each pair should be " 
  
 + 
  
 "separated by an equal sign, for example: 'gad_campaignid=12345 gad_source=1'. Only " 
  
 + 
  
 "one of sessionAttributesEncoded or sessionAttributesMap should be passed." 
 ) 
  
 private 
  
 String 
  
 sessionAttributesMap 
 ; 
  
 } 
  
 public 
  
 static 
  
 void 
  
 main 
 ( 
 String 
 [] 
  
 args 
 ) 
  
 throws 
  
 UnsupportedEncodingException 
 , 
  
 NoSuchAlgorithmException 
  
 { 
  
 UploadEnhancedConversionsForLeadsParams 
  
 params 
  
 = 
  
 new 
  
 UploadEnhancedConversionsForLeadsParams 
 (); 
  
 if 
  
 ( 
 ! 
 params 
 . 
 parseArguments 
 ( 
 args 
 )) 
  
 { 
  
 // Either pass the required parameters for this example on the command line, or insert them 
  
 // into the code here. See the parameter class definition above for descriptions. 
  
 params 
 . 
 customerId 
  
 = 
  
 Long 
 . 
 parseLong 
 ( 
 "INSERT_CUSTOMER_ID_HERE" 
 ); 
  
 params 
 . 
 conversionActionId 
  
 = 
  
 Long 
 . 
 parseLong 
 ( 
 "INSERT_CONVERSION_ACTION_ID_HERE" 
 ); 
  
 params 
 . 
 conversionDateTime 
  
 = 
  
 "INSERT_CONVERSION_DATE_TIME_HERE" 
 ; 
  
 params 
 . 
 conversionValue 
  
 = 
  
 Double 
 . 
 parseDouble 
 ( 
 "INSERT_CONVERSION_VALUE_HERE" 
 ); 
  
 // Optional: Specify the unique order ID for the click conversion. 
  
 params 
 . 
 orderId 
  
 = 
  
 null 
 ; 
  
 // Optional: specify the Google click ID (gclid) for the click. 
  
 params 
 . 
 gclid 
  
 = 
  
 null 
 ; 
  
 // Optional: specify the ad user data consent for the click. 
  
 params 
 . 
 adUserDataConsent 
  
 = 
  
 null 
 ; 
  
 params 
 . 
 sessionAttributesEncoded 
  
 = 
  
 null 
 ; 
  
 params 
 . 
 sessionAttributesMap 
  
 = 
  
 null 
 ; 
  
 } 
  
 if 
  
 (( 
 params 
 . 
 sessionAttributesEncoded 
  
 != 
  
 null 
 ) 
 && 
 ( 
 params 
 . 
 sessionAttributesMap 
  
 != 
  
 null 
 )) 
  
 { 
  
 throw 
  
 new 
  
 IllegalArgumentException 
 ( 
  
 "Only one of sessionAttributesEncoded or " 
  
 + 
  
 "sessionAttributesMap can be set." 
 ); 
  
 } 
  
 GoogleAdsClient 
  
 googleAdsClient 
  
 = 
  
 null 
 ; 
  
 try 
  
 { 
  
 googleAdsClient 
  
 = 
  
 GoogleAdsClient 
 . 
 newBuilder 
 (). 
 fromPropertiesFile 
 (). 
 build 
 (); 
  
 } 
  
 catch 
  
 ( 
 FileNotFoundException 
  
 fnfe 
 ) 
  
 { 
  
 System 
 . 
 err 
 . 
 printf 
 ( 
  
 "Failed to load GoogleAdsClient configuration from file. Exception: %s%n" 
 , 
  
 fnfe 
 ); 
  
 System 
 . 
 exit 
 ( 
 1 
 ); 
  
 } 
  
 catch 
  
 ( 
 IOException 
  
 ioe 
 ) 
  
 { 
  
 System 
 . 
 err 
 . 
 printf 
 ( 
 "Failed to create GoogleAdsClient. Exception: %s%n" 
 , 
  
 ioe 
 ); 
  
 System 
 . 
 exit 
 ( 
 1 
 ); 
  
 } 
  
 try 
  
 { 
  
 new 
  
 UploadEnhancedConversionsForLeads 
 () 
  
 . 
 runExample 
 ( 
  
 googleAdsClient 
 , 
  
 params 
 . 
 customerId 
 , 
  
 params 
 . 
 conversionActionId 
 , 
  
 params 
 . 
 conversionDateTime 
 , 
  
 params 
 . 
 conversionValue 
 , 
  
 params 
 . 
 orderId 
 , 
  
 params 
 . 
 gclid 
 , 
  
 params 
 . 
 adUserDataConsent 
 , 
  
 params 
 . 
 sessionAttributesEncoded 
 , 
  
 params 
 . 
 sessionAttributesMap 
 ); 
  
 } 
  
 catch 
  
 ( 
 GoogleAdsException 
  
 gae 
 ) 
  
 { 
  
 // GoogleAdsException is the base class for most exceptions thrown by an API request. 
  
 // Instances of this exception have a message and a GoogleAdsFailure that contains a 
  
 // collection of GoogleAdsErrors that indicate the underlying causes of the 
  
 // GoogleAdsException. 
  
 System 
 . 
 err 
 . 
 printf 
 ( 
  
 "Request ID %s failed due to GoogleAdsException. Underlying errors:%n" 
 , 
  
 gae 
 . 
 getRequestId 
 ()); 
  
 int 
  
 i 
  
 = 
  
 0 
 ; 
  
 for 
  
 ( 
 GoogleAdsError 
  
 googleAdsError 
  
 : 
  
 gae 
 . 
 getGoogleAdsFailure 
 (). 
 getErrorsList 
 ()) 
  
 { 
  
 System 
 . 
 err 
 . 
 printf 
 ( 
 "  Error %d: %s%n" 
 , 
  
 i 
 ++ 
 , 
  
 googleAdsError 
 ); 
  
 } 
  
 System 
 . 
 exit 
 ( 
 1 
 ); 
  
 } 
  
 } 
  
 /** 
 * Runs the example. 
 * 
 * @param googleAdsClient the Google Ads API client. 
 * @param customerId the client customer ID. 
 * @param conversionActionId conversion action ID associated with this conversion. 
 * @param conversionDateTime date and time of the conversion. 
 * @param conversionValue the value of the conversion. 
 * @param orderId the unique ID (transaction ID) of the conversion. 
 * @param gclid the Google click ID of the conversion. 
 * @param adUserDataConsent the ad user data consent for the click 
 */ 
  
 private 
  
 void 
  
 runExample 
 ( 
  
 GoogleAdsClient 
  
 googleAdsClient 
 , 
  
 long 
  
 customerId 
 , 
  
 long 
  
 conversionActionId 
 , 
  
 String 
  
 conversionDateTime 
 , 
  
 Double 
  
 conversionValue 
 , 
  
 String 
  
 orderId 
 , 
  
 String 
  
 gclid 
 , 
  
 ConsentStatus 
  
 adUserDataConsent 
 , 
  
 String 
  
 sessionAttributesEncoded 
 , 
  
 String 
  
 sessionAttributesMap 
 ) 
  
 throws 
  
 UnsupportedEncodingException 
 , 
  
 NoSuchAlgorithmException 
  
 { 
  
 // Creates an empty builder for constructing the click conversion. 
  
 ClickConversion 
 . 
 Builder 
  
 clickConversionBuilder 
  
 = 
  
 ClickConversion 
 . 
 newBuilder 
 (); 
  
 // Extracts user email and phone from the raw data, normalizes and hashes it, then wraps it in 
  
 // UserIdentifier objects. 
  
 // Creates a separate UserIdentifier object for each. The data in this example is hardcoded, but 
  
 // in your application you might read the raw data from an input file. 
  
 // IMPORTANT: Since the identifier attribute of UserIdentifier 
  
 // (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier) is a 
  
 // oneof 
  
 // (https://protobuf.dev/programming-guides/proto3/#oneof-features), you must set only ONE of 
  
 // hashedEmail, hashedPhoneNumber, mobileId, thirdPartyUserId, or addressInfo. Setting more 
  
 // than one of these attributes on the same UserIdentifier will clear all the other members 
  
 // of the oneof. For example, the following code is INCORRECT and will result in a 
  
 // UserIdentifier with ONLY a hashedPhoneNumber. 
  
 // 
  
 // UserIdentifier incorrectlyPopulatedUserIdentifier = 
  
 //     UserIdentifier.newBuilder() 
  
 //         .setHashedEmail("...") 
  
 //         .setHashedPhoneNumber("...") 
  
 //         .build(); 
  
 ImmutableMap 
 . 
 Builder<String 
 , 
  
 String 
>  
 rawRecordBuilder 
  
 = 
  
 ImmutableMap 
 . 
< String 
 , 
  
 String>builder 
 () 
  
 . 
 put 
 ( 
 "email" 
 , 
  
 "alex.2@example.com" 
 ) 
  
 // Phone number to be converted to E.164 format, with a leading '+' as required. 
  
 . 
 put 
 ( 
 "phone" 
 , 
  
 "+1 800 5550102" 
 ) 
  
 // This example lets you put conversion details as arguments, but in reality you might 
  
 // store this data alongside other user data, so we include it in this sample user 
  
 // record. 
  
 . 
 put 
 ( 
 "conversionActionId" 
 , 
  
 Long 
 . 
 toString 
 ( 
 conversionActionId 
 )) 
  
 . 
 put 
 ( 
 "conversionDateTime" 
 , 
  
 conversionDateTime 
 ) 
  
 . 
 put 
 ( 
 "conversionValue" 
 , 
  
 Double 
 . 
 toString 
 ( 
 conversionValue 
 )) 
  
 . 
 put 
 ( 
 "currencyCode" 
 , 
  
 "USD" 
 ); 
  
 // Adds entries for the optional fields. 
  
 if 
  
 ( 
 orderId 
  
 != 
  
 null 
 ) 
  
 { 
  
 rawRecordBuilder 
 . 
 put 
 ( 
 "orderId" 
 , 
  
 orderId 
 ); 
  
 } 
  
 if 
  
 ( 
 gclid 
  
 != 
  
 null 
 ) 
  
 { 
  
 rawRecordBuilder 
 . 
 put 
 ( 
 "gclid" 
 , 
  
 gclid 
 ); 
  
 } 
  
 if 
  
 ( 
 adUserDataConsent 
  
 != 
  
 null 
 ) 
  
 { 
  
 rawRecordBuilder 
 . 
 put 
 ( 
 "adUserDataConsent" 
 , 
  
 adUserDataConsent 
 . 
 name 
 ()); 
  
 } 
  
 if 
  
 ( 
 sessionAttributesEncoded 
  
 != 
  
 null 
 ) 
  
 { 
  
 rawRecordBuilder 
 . 
 put 
 ( 
 "sessionAttributesEncoded" 
 , 
  
 sessionAttributesEncoded 
 ); 
  
 } 
  
 if 
  
 ( 
 sessionAttributesMap 
  
 != 
  
 null 
 ) 
  
 { 
  
 rawRecordBuilder 
 . 
 put 
 ( 
 "sessionAttributesMap" 
 , 
  
 sessionAttributesMap 
 ); 
  
 } 
  
 // Builds the map representing the record. 
  
 Map<String 
 , 
  
 String 
>  
 rawRecord 
  
 = 
  
 rawRecordBuilder 
 . 
 build 
 (); 
  
 // Creates a SHA256 message digest for hashing user identifiers in a privacy-safe way, as 
  
 // described at https://support.google.com/google-ads/answer/9888656. 
  
 MessageDigest 
  
 sha256Digest 
  
 = 
  
 MessageDigest 
 . 
 getInstance 
 ( 
 "SHA-256" 
 ); 
  
 // Creates a list for the user identifiers. 
  
 List<UserIdentifier> 
  
 userIdentifiers 
  
 = 
  
 new 
  
 ArrayList 
<> (); 
  
 // Creates a user identifier using the hashed email address, using the normalize and hash method 
  
 // specifically for email addresses. 
  
 UserIdentifier 
  
 emailIdentifier 
  
 = 
  
 UserIdentifier 
 . 
 newBuilder 
 () 
  
 // Optional: specify the user identifier source. 
  
 . 
 setUserIdentifierSource 
 ( 
 UserIdentifierSource 
 . 
 FIRST_PARTY 
 ) 
  
 // Uses the normalize and hash method specifically for email addresses. 
  
 . 
 setHashedEmail 
 ( 
 normalizeAndHashEmailAddress 
 ( 
 sha256Digest 
 , 
  
 rawRecord 
 . 
 get 
 ( 
 "email" 
 ))) 
  
 . 
 build 
 (); 
  
 userIdentifiers 
 . 
 add 
 ( 
 emailIdentifier 
 ); 
  
 // Creates a user identifier using normalized and hashed phone info. 
  
 UserIdentifier 
  
 hashedPhoneNumberIdentifier 
  
 = 
  
 UserIdentifier 
 . 
 newBuilder 
 () 
  
 . 
 setHashedPhoneNumber 
 ( 
 normalizeAndHash 
 ( 
 sha256Digest 
 , 
  
 rawRecord 
 . 
 get 
 ( 
 "phone" 
 ))) 
  
 . 
 build 
 (); 
  
 // Adds the hashed phone number identifier to the UserData object's list. 
  
 userIdentifiers 
 . 
 add 
 ( 
 hashedPhoneNumberIdentifier 
 ); 
  
 // Adds the user identifiers to the conversion. 
  
 clickConversionBuilder 
 . 
 addAllUserIdentifiers 
 ( 
 userIdentifiers 
 ); 
  
 // Adds details of the conversion. 
  
 clickConversionBuilder 
 . 
 setConversionAction 
 ( 
  
 ResourceNames 
 . 
 conversionAction 
 ( 
  
 customerId 
 , 
  
 Long 
 . 
 parseLong 
 ( 
 rawRecord 
 . 
 get 
 ( 
 "conversionActionId" 
 )))); 
  
 clickConversionBuilder 
 . 
 setConversionDateTime 
 ( 
 rawRecord 
 . 
 get 
 ( 
 "conversionDateTime" 
 )); 
  
 clickConversionBuilder 
 . 
 setConversionValue 
 ( 
 Double 
 . 
 parseDouble 
 ( 
 rawRecord 
 . 
 get 
 ( 
 "conversionValue" 
 ))); 
  
 clickConversionBuilder 
 . 
 setCurrencyCode 
 ( 
 rawRecord 
 . 
 get 
 ( 
 "currencyCode" 
 )); 
  
 // Sets the order ID if provided. 
  
 if 
  
 ( 
 rawRecord 
 . 
 containsKey 
 ( 
 "orderId" 
 )) 
  
 { 
  
 clickConversionBuilder 
 . 
 setOrderId 
 ( 
 rawRecord 
 . 
 get 
 ( 
 "orderId" 
 )); 
  
 } 
  
 // Sets the Google click ID (gclid) if provided. 
  
 if 
  
 ( 
 rawRecord 
 . 
 containsKey 
 ( 
 "gclid" 
 )) 
  
 { 
  
 clickConversionBuilder 
 . 
 setGclid 
 ( 
 rawRecord 
 . 
 get 
 ( 
 "gclid" 
 )); 
  
 } 
  
 // Sets the consent information, if provided. 
  
 if 
  
 ( 
 rawRecord 
 . 
 containsKey 
 ( 
 "adUserDataConsent" 
 )) 
  
 { 
  
 // Specifies whether user consent was obtained for the data you are uploading. See 
  
 // https://www.google.com/about/company/user-consent-policy for details. 
  
 clickConversionBuilder 
 . 
 setConsent 
 ( 
  
 Consent 
 . 
 newBuilder 
 () 
  
 . 
 setAdUserData 
 ( 
 ConsentStatus 
 . 
 valueOf 
 ( 
 rawRecord 
 . 
 get 
 ( 
 "adUserDataConsent" 
 )))); 
  
 } 
  
 // Sets one of the sessionAttributesEncoded or sessionAttributesKeyValuePairs if either is 
  
 // provided. 
  
 if 
  
 ( 
 rawRecord 
 . 
 containsKey 
 ( 
 "sessionAttributesEncoded" 
 )) 
  
 { 
  
 clickConversionBuilder 
 . 
 setSessionAttributesEncoded 
 ( 
  
 ByteString 
 . 
 copyFromUtf8 
 ( 
 rawRecord 
 . 
 get 
 ( 
 "sessionAttributesEncoded" 
 ))); 
  
 } 
  
 else 
  
 if 
  
 ( 
 rawRecord 
 . 
 containsKey 
 ( 
 "sessionAttributesMap" 
 )) 
  
 { 
  
 List<String> 
  
 pairings 
  
 = 
  
 Arrays 
 . 
 stream 
 ( 
 rawRecord 
 . 
 get 
 ( 
 "sessionAttributesMap" 
 ). 
 split 
 ( 
 " " 
 )) 
  
 . 
 map 
 ( 
 String 
 :: 
 trim 
 ) 
  
 . 
 collect 
 ( 
 Collectors 
 . 
 toList 
 ()); 
  
 SessionAttributesKeyValuePairs 
 . 
 Builder 
  
 sessionAttributePairs 
  
 = 
  
 SessionAttributesKeyValuePairs 
 . 
 newBuilder 
 (); 
  
 for 
  
 ( 
 String 
  
 pair 
  
 : 
  
 pairings 
 ) 
  
 { 
  
 String 
 [] 
  
 parts 
  
 = 
  
 pair 
 . 
 split 
 ( 
 "=" 
 , 
  
 2 
 ); 
  
 if 
  
 ( 
 parts 
 . 
 length 
  
 != 
  
 2 
 ) 
  
 { 
  
 throw 
  
 new 
  
 IllegalArgumentException 
 ( 
  
 "Failed to read the sessionAttributesMap. SessionAttributesMap must use a " 
  
 + 
  
 "space-delimited list of session attribute key value pairs. Each pair should be" 
  
 + 
  
 " separated by an equal sign, for example: 'gad_campaignid=12345 gad_source=1'" 
 ); 
  
 } 
  
 sessionAttributePairs 
 . 
 addKeyValuePairs 
 ( 
  
 SessionAttributeKeyValuePair 
 . 
 newBuilder 
 () 
  
 . 
 setSessionAttributeKey 
 ( 
 parts 
 [ 
 0 
 ] 
 ) 
  
 . 
 setSessionAttributeValue 
 ( 
 parts 
 [ 
 1 
 ] 
 ) 
  
 . 
 build 
 ()); 
  
 } 
  
 clickConversionBuilder 
 . 
 setSessionAttributesKeyValuePairs 
 ( 
 sessionAttributePairs 
 . 
 build 
 ()); 
  
 } 
  
 // Calls build to build the conversion. 
  
 ClickConversion 
  
 clickConversion 
  
 = 
  
 clickConversionBuilder 
 . 
 build 
 (); 
  
 // Creates the conversion upload service client. 
  
 try 
  
 ( 
 ConversionUploadServiceClient 
  
 conversionUploadServiceClient 
  
 = 
  
 googleAdsClient 
 . 
 getLatestVersion 
 (). 
 createConversionUploadServiceClient 
 ()) 
  
 { 
  
 // Uploads the click conversion. Partial failure should always be set to true. 
  
 // NOTE: This request contains a single conversion as a demonstration.  However, if you have 
  
 // multiple conversions to upload, it's best to upload multiple conversions per request 
  
 // instead of sending a separate request per conversion. See the following for per-request 
  
 // limits: 
  
 // https://developers.google.com/google-ads/api/docs/best-practices/quotas#conversion_upload_service 
  
 UploadClickConversionsResponse 
  
 response 
  
 = 
  
 conversionUploadServiceClient 
 . 
 uploadClickConversions 
 ( 
  
 UploadClickConversionsRequest 
 . 
 newBuilder 
 () 
  
 . 
 setCustomerId 
 ( 
 Long 
 . 
 toString 
 ( 
 customerId 
 )) 
  
 . 
 addConversions 
 ( 
 clickConversion 
 ) 
  
 // Enables partial failure (must be true). 
  
 . 
 setPartialFailure 
 ( 
 true 
 ) 
  
 . 
 build 
 ()); 
  
 // Prints any partial errors returned. 
  
 // To review the overall health of your recent uploads, see: 
  
 // https://developers.google.com/google-ads/api/docs/conversions/upload-summaries 
  
 if 
  
 ( 
 response 
 . 
 hasPartialFailureError 
 ()) 
  
 { 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
  
 "Partial error encountered: '%s'.%n" 
 , 
  
 response 
 . 
 getPartialFailureError 
 (). 
 getMessage 
 ()); 
  
 } 
  
 // Prints the result. 
  
 ClickConversionResult 
  
 result 
  
 = 
  
 response 
 . 
 getResults 
 ( 
 0 
 ); 
  
 // Only prints valid results. 
  
 if 
  
 ( 
 result 
 . 
 hasConversionDateTime 
 ()) 
  
 { 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
  
 "Uploaded conversion that occurred at '%s' to '%s'.%n" 
 , 
  
 result 
 . 
 getConversionDateTime 
 (), 
  
 result 
 . 
 getConversionAction 
 ()); 
  
 } 
  
 } 
  
 } 
  
 /** 
 * Returns the result of normalizing and then hashing the string using the provided digest. 
 * Private customer data must be hashed during upload, as described at 
 * https://support.google.com/google-ads/answer/7474263. 
 * 
 * @param digest the digest to use to hash the normalized string. 
 * @param s the string to normalize and hash. 
 */ 
  
 private 
  
 String 
  
 normalizeAndHash 
 ( 
 MessageDigest 
  
 digest 
 , 
  
 String 
  
 s 
 ) 
  
 throws 
  
 UnsupportedEncodingException 
  
 { 
  
 // Normalizes by first converting all characters to lowercase, then trimming spaces. 
  
 String 
  
 normalized 
  
 = 
  
 s 
 . 
 toLowerCase 
 (); 
  
 // Removes leading, trailing, and intermediate spaces. 
  
 normalized 
  
 = 
  
 normalized 
 . 
 replaceAll 
 ( 
 "\\s+" 
 , 
  
 "" 
 ); 
  
 // Hashes the normalized string using the hashing algorithm. 
  
 byte 
 [] 
  
 hash 
  
 = 
  
 digest 
 . 
 digest 
 ( 
 normalized 
 . 
 getBytes 
 ( 
 "UTF-8" 
 )); 
  
 StringBuilder 
  
 result 
  
 = 
  
 new 
  
 StringBuilder 
 (); 
  
 for 
  
 ( 
 byte 
  
 b 
  
 : 
  
 hash 
 ) 
  
 { 
  
 result 
 . 
 append 
 ( 
 String 
 . 
 format 
 ( 
 "%02x" 
 , 
  
 b 
 )); 
  
 } 
  
 return 
  
 result 
 . 
 toString 
 (); 
  
 } 
  
 /** 
 * Returns the result of normalizing and hashing an email address. For this use case, Google Ads 
 * requires removal of any '.' characters preceding {@code gmail.com} or {@code googlemail.com}. 
 * 
 * @param digest the digest to use to hash the normalized string. 
 * @param emailAddress the email address to normalize and hash. 
 */ 
  
 private 
  
 String 
  
 normalizeAndHashEmailAddress 
 ( 
 MessageDigest 
  
 digest 
 , 
  
 String 
  
 emailAddress 
 ) 
  
 throws 
  
 UnsupportedEncodingException 
  
 { 
  
 String 
  
 normalizedEmail 
  
 = 
  
 emailAddress 
 . 
 toLowerCase 
 (); 
  
 String 
 [] 
  
 emailParts 
  
 = 
  
 normalizedEmail 
 . 
 split 
 ( 
 "@" 
 ); 
  
 if 
  
 ( 
 emailParts 
 . 
 length 
 > 
 1 
 && 
 emailParts 
 [ 
 1 
 ] 
 . 
 matches 
 ( 
 "^(gmail|googlemail)\\.com\\s*" 
 )) 
  
 { 
  
 // Removes any '.' characters from the portion of the email address before the domain if the 
  
 // domain is gmail.com or googlemail.com. 
  
 emailParts 
 [ 
 0 
 ] 
  
 = 
  
 emailParts 
 [ 
 0 
 ] 
 . 
 replaceAll 
 ( 
 "\\." 
 , 
  
 "" 
 ); 
  
 normalizedEmail 
  
 = 
  
 String 
 . 
 format 
 ( 
 "%s@%s" 
 , 
  
 emailParts 
 [ 
 0 
 ] 
 , 
  
 emailParts 
 [ 
 1 
 ] 
 ); 
  
 } 
  
 return 
  
 normalizeAndHash 
 ( 
 digest 
 , 
  
 normalizedEmail 
 ); 
  
 } 
 } 
  
  

C#

 // Copyright 2021 Google LLC 
 // 
 // Licensed under the Apache License, Version 2.0 (the "License"); 
 // you may not use this file except in compliance with the License. 
 // You may obtain a copy of the License at 
 // 
 //     http://www.apache.org/licenses/LICENSE-2.0 
 // 
 // Unless required by applicable law or agreed to in writing, software 
 // distributed under the License is distributed on an "AS IS" BASIS, 
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 // See the License for the specific language governing permissions and 
 // limitations under the License. 
 using 
  
 CommandLine 
 ; 
 using 
  
 Google.Ads.Gax.Examples 
 ; 
 using 
  
 Google.Ads.GoogleAds.Lib 
 ; 
 using 
  
 Google.Ads.GoogleAds.V21.Common 
 ; 
 using 
  
 Google.Ads.GoogleAds.V21.Errors 
 ; 
 using 
  
 Google.Ads.GoogleAds.V21.Services 
 ; 
 using 
  
 Google.Protobuf 
 ; 
 using 
  
 System 
 ; 
 using 
  
 System.Collections.Generic 
 ; 
 using 
  
 System.Linq 
 ; 
 using 
  
 System.Security.Cryptography 
 ; 
 using 
  
 System.Text 
 ; 
 using 
  
 static 
  
 Google 
 . 
 Ads 
 . 
 GoogleAds 
 . 
 V21 
 . 
 Enums 
 . 
 UserIdentifierSourceEnum 
 . 
 Types 
 ; 
 namespace 
  
 Google.Ads.GoogleAds.Examples.V21 
 { 
  
 /// <summary> 
  
 /// This code example uploads an enhanced conversion for leads by uploading a ClickConversion 
  
 /// with user identifiers, and optionally a click ID and order ID, so Google can more accurately 
  
 /// tie the conversion to the ad that drove the lead. 
  
 /// </summary> 
  
 public 
  
 class 
  
 UploadEnhancedConversionsForLeads 
  
 : 
  
 ExampleBase 
  
 { 
  
 /// <summary> 
  
 /// Command line options for running the <see cref="UploadEnhancedConversionsForLeads"/> example. 
  
 /// </summary> 
  
 public 
  
 class 
  
 Options 
  
 : 
  
 OptionsBase 
  
 { 
  
 /// <summary> 
  
 /// The Google Ads customer ID for which conversions are uploaded. 
  
 /// </summary> 
  
 [Option("customerId", Required = true, HelpText = 
 "The Google Ads customer ID for which conversions are uploaded.")] 
  
 public 
  
 long 
  
 CustomerId 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 /// <summary> 
  
 /// ID of the conversion action for which conversions are uploaded. 
  
 /// </summary> 
  
 [Option("conversionActionId", Required = true, HelpText = 
 "ID of the conversion action for which conversions are uploaded.")] 
  
 public 
  
 long 
  
 ConversionActionId 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 /// <summary> 
  
 /// The date time at which the conversion occurred. Must be after the click time, 
  
 /// and must include the time zone offset. The format is 
  
 /// 'yyyy-mm-dd hh:mm:ss+|-hh:mm', e.g. '2019-01-01 12:32:45-08:00'. 
  
 /// </summary> 
  
 [Option("conversionDateTime", Required = true, HelpText = 
 "The date time at which the conversion occurred. Must be after the click time, " + 
 "and must include the time zone offset. The format is " + 
 "'yyyy-mm-dd hh:mm:ss+|-hh:mm', e.g. '2019-01-01 12:32:45-08:00'.")] 
  
 public 
  
 string 
  
 ConversionDateTime 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 /// <summary> 
  
 /// The conversion value. 
  
 /// </summary> 
  
 [Option("conversionValue", Required = true, HelpText = 
 "The conversion value.")] 
  
 public 
  
 double 
  
 ConversionValue 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 /// <summary> 
  
 /// The unique order ID (transaction ID) of the conversion. 
  
 /// </summary> 
  
 [Option("orderId", Required = false, HelpText = 
 "The unique order ID (transaction ID) of the conversion.")] 
  
 public 
  
 string 
  
 OrderId 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 /// <summary> 
  
 /// The Google click ID. 
  
 /// </summary> 
  
 [Option("gclid", Required = false, HelpText = 
 "The Google click ID.")] 
  
 public 
  
 string 
  
 Gclid 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 /// <summary> 
  
 /// A string token of encoded session attributes. Only one of sessionAttributesEncoded 
  
 /// or sessionAttributes should be passed. 
  
 /// </summary> 
  
 [Option("sessionAttributesEncoded", Required = false, HelpText = 
 "A string token of encoded session attributes. Only one of " + 
 "SessionAttributesEncoded or SessionAttributesJson should be passed.")] 
  
 public 
  
 string 
  
 SessionAttributesEncoded 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 /// <summary> 
  
 /// A string of session attribute key value pairs, in the form 
  
 /// key1=value1;key2=value2 etc.. Only one of sessionAttributesEncoded or 
  
 /// sessionAttributes should be passed. 
  
 /// </summary> 
  
 [Option("sessionAttributes", Required = false, HelpText = 
 "A string of session attribute key value pairs, in the form " + 
 "key1=value1;key2=value2 etc.. Only one of SessionAttributesEncoded or " + 
 "SessionAttributes should be passed.")] 
  
 public 
  
 string 
  
 SessionAttributes 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 } 
  
 /// <summary> 
  
 /// Main method, to run this code example as a standalone application. 
  
 /// </summary> 
  
 /// <param name="args">The command line arguments.</param> 
  
 public 
  
 static 
  
 void 
  
 Main 
 ( 
 string 
 [] 
  
 args 
 ) 
  
 { 
  
 Options 
  
 options 
  
 = 
  
 ExampleUtilities 
 . 
 ParseCommandLine<Options> 
 ( 
 args 
 ); 
  
 UploadEnhancedConversionsForLeads 
  
 codeExample 
  
 = 
  
 new 
  
 UploadEnhancedConversionsForLeads 
 (); 
  
 Console 
 . 
 WriteLine 
 ( 
 codeExample 
 . 
 Description 
 ); 
  
 codeExample 
 . 
 Run 
 ( 
 new 
  
 GoogleAdsClient 
 (), 
  
 options 
 . 
 CustomerId 
 , 
  
 options 
 . 
 ConversionActionId 
 , 
  
 options 
 . 
 ConversionDateTime 
 , 
  
 options 
 . 
 ConversionValue 
 , 
  
 options 
 . 
 OrderId 
 , 
  
 options 
 . 
 Gclid 
 , 
  
 options 
 . 
 SessionAttributesEncoded 
 , 
  
 options 
 . 
 SessionAttributes 
 ); 
  
 } 
  
 private 
  
 static 
  
 SHA256 
  
 digest 
  
 = 
  
 SHA256 
 . 
 Create 
 (); 
  
 /// <summary> 
  
 /// Returns a description about the code example. 
  
 /// </summary> 
  
 public 
  
 override 
  
 string 
  
 Description 
  
 = 
>  
 "This code example uploads an enhanced conversion for leads by uploading a " 
  
 + 
  
 "ClickConversion with user identifiers, and optionally a click ID and order ID, so " 
  
 + 
  
 "Google can more accurately tie the conversion to the ad that drove the lead." 
 ; 
  
 /// <summary> 
  
 /// Runs the code example. 
  
 /// </summary> 
  
 /// <param name="client">The Google Ads client.</param> 
  
 /// <param name="customerId">The Google Ads customer ID for which conversions are uploaded. 
  
 /// </param> 
  
 /// <param name="conversionActionId">ID of the conversion action for which conversions are 
  
 /// uploaded.</param> 
  
 /// <param name="conversionDateTime">The date time at which the conversion occurred.</param> 
  
 /// <param name="conversionValue">The conversion value.</param> 
  
 /// <param name="orderId">The unique order ID (transaction ID) of the conversion.</param> 
  
 /// <param name="gclid">The Google click ID</param> 
  
 /// <param name="sessionAttributesEncoded">The encoded session attributes.</param> 
  
 /// <param name="sessionAttributes">The string for session attributes.</param> 
  
 public 
  
 void 
  
 Run 
 ( 
 GoogleAdsClient 
  
 client 
 , 
  
 long 
  
 customerId 
 , 
  
 long 
  
 conversionActionId 
 , 
  
 string 
  
 conversionDateTime 
 , 
  
 double 
  
 conversionValue 
 , 
  
 string 
  
 orderId 
 , 
  
 string 
  
 gclid 
 , 
  
 string 
  
 sessionAttributesEncoded 
 , 
  
 string 
  
 sessionAttributes 
 ) 
  
 { 
  
 if 
  
 ( 
 ! 
 string 
 . 
 IsNullOrEmpty 
 ( 
 sessionAttributesEncoded 
 ) 
  
&&  
 ! 
 string 
 . 
 IsNullOrEmpty 
 ( 
 sessionAttributes 
 )) 
  
 { 
  
 throw 
  
 new 
  
 InvalidOperationException 
 ( 
 "Only one of SessionAttributesEncoded or " 
  
 + 
  
 "SessionAttributes can be set" 
 ); 
  
 } 
  
 // Get the ConversionUploadService. 
  
 ConversionUploadServiceClient 
  
 conversionUploadService 
  
 = 
  
 client 
 . 
 GetService 
 ( 
 Services 
 . 
 V21 
 . 
 ConversionUploadService 
 ); 
  
 // Creates an empty click conversion. 
  
 ClickConversion 
  
 clickConversion 
  
 = 
  
 new 
  
 ClickConversion 
 (); 
  
 // Gets the conversion action resource name. 
  
 /*string conversionActionResourceName = 
 // Creates a builder for constructing the click conversion. 
 ClickConversion clickConversion = new ClickConversion() 
 { 
 ConversionAction = conversionActionResourceName, 
 ConversionDateTime = conversionDateTime, 
 ConversionValue = conversionValue, 
 CurrencyCode = "USD", 
 // Specifies whether user consent was obtained for the data you are uploading. See 
 // https://www.google.com/about/company/user-consent-policy 
 // for details. 
 Consent = new Consent() 
 { 
 AdPersonalization = ConsentStatus.Granted, 
 AdUserData = ConsentStatus.Denied 
 } 
 }; 
 // Sets the order ID if provided. 
 if (!string.IsNullOrEmpty(orderId)) 
 { 
 clickConversion.OrderId = orderId; 
 }*/ 
  
 // Adds a user identifier using the hashed email address, using the normalize 
  
 // and hash method specifically for email addresses. 
  
 clickConversion 
 . 
 UserIdentifiers 
 . 
 Add 
 ( 
 new 
  
 UserIdentifier 
 () 
  
 { 
  
 HashedEmail 
  
 = 
  
 NormalizeAndHashEmailAddress 
 ( 
 "alex.2@example.com" 
 ), 
  
 // Optional: Specifies the user identifier source. 
  
 UserIdentifierSource 
  
 = 
  
 UserIdentifierSource 
 . 
 FirstParty 
  
 }); 
  
 // Adds a user identifier using normalized and hashed phone info. 
  
 clickConversion 
 . 
 UserIdentifiers 
 . 
 Add 
 ( 
 new 
  
 UserIdentifier 
 () 
  
 { 
  
 HashedPhoneNumber 
  
 = 
  
 NormalizeAndHash 
 ( 
 "+1 800 5550102" 
 ), 
  
 // Optional: Specifies the user identifier source. 
  
 UserIdentifierSource 
  
 = 
  
 UserIdentifierSource 
 . 
 FirstParty 
  
 }); 
  
 // Adds a user identifier with all the required mailing address elements. 
  
 clickConversion 
 . 
 UserIdentifiers 
 . 
 Add 
 ( 
 new 
  
 UserIdentifier 
 () 
  
 { 
  
 AddressInfo 
  
 = 
  
 new 
  
 OfflineUserAddressInfo 
 () 
  
 { 
  
 // FirstName and LastName must be normalized and hashed. 
  
 HashedFirstName 
  
 = 
  
 NormalizeAndHash 
 ( 
 "Alex" 
 ), 
  
 HashedLastName 
  
 = 
  
 NormalizeAndHash 
 ( 
 "Quinn" 
 ), 
  
 // CountryCode and PostalCode are sent in plain text. 
  
 CountryCode 
  
 = 
  
 "US" 
 , 
  
 PostalCode 
  
 = 
  
 "94045" 
  
 } 
  
 }); 
  
 // Adds details of the conversion. 
  
 clickConversion 
 . 
 ConversionAction 
  
 = 
  
 ResourceNames 
 . 
 ConversionAction 
 ( 
 customerId 
 , 
  
 conversionActionId 
 ); 
  
 clickConversion 
 . 
 ConversionDateTime 
  
 = 
  
 conversionDateTime 
 ; 
  
 clickConversion 
 . 
 ConversionValue 
  
 = 
  
 conversionValue 
 ; 
  
 clickConversion 
 . 
 CurrencyCode 
  
 = 
  
 "USD" 
 ; 
  
 // Sets the order ID if provided. 
  
 if 
  
 ( 
 ! 
 string 
 . 
 IsNullOrEmpty 
 ( 
 orderId 
 )) 
  
 { 
  
 clickConversion 
 . 
 OrderId 
  
 = 
  
 orderId 
 ; 
  
 } 
  
 // Sets the Google click ID (gclid) if provided. 
  
 if 
  
 ( 
 ! 
 string 
 . 
 IsNullOrEmpty 
 ( 
 gclid 
 )) 
  
 { 
  
 clickConversion 
 . 
 Gclid 
  
 = 
  
 gclid 
 ; 
  
 } 
  
 if 
  
 ( 
 ! 
 string 
 . 
 IsNullOrEmpty 
 ( 
 sessionAttributesEncoded 
 )) 
  
 { 
  
 clickConversion 
 . 
 SessionAttributesEncoded 
  
 = 
  
 ByteString 
 . 
 CopyFrom 
 ( 
 sessionAttributesEncoded 
 , 
  
 Encoding 
 . 
 Unicode 
 ); 
  
 } 
  
 else 
  
 if 
  
 ( 
 ! 
 string 
 . 
 IsNullOrEmpty 
 ( 
 sessionAttributes 
 )) 
  
 { 
  
 IEnumerable<SessionAttributeKeyValuePair> 
  
 parsedSessionAttributes 
  
 = 
  
 sessionAttributes 
 . 
 Split 
 ( 
 ';' 
 ). 
 Select 
 ( 
 pair 
  
 = 
>  
 { 
  
 string 
 [] 
  
 split 
  
 = 
  
 pair 
 . 
 Split 
 ( 
 '=' 
 ); 
  
 return 
  
 new 
  
 SessionAttributeKeyValuePair 
 () 
  
 { 
  
 SessionAttributeKey 
  
 = 
  
 split 
 [ 
 0 
 ], 
  
 SessionAttributeValue 
  
 = 
  
 split 
 [ 
 1 
 ] 
  
 }; 
  
 }); 
  
 clickConversion 
 . 
 SessionAttributesKeyValuePairs 
  
 = 
  
 new 
  
 SessionAttributesKeyValuePairs 
 (); 
  
 clickConversion 
 . 
 SessionAttributesKeyValuePairs 
 . 
 KeyValuePairs 
  
 . 
 AddRange 
 ( 
 parsedSessionAttributes 
 ); 
  
 } 
  
 try 
  
 { 
  
 // Uploads the click conversion. Partial failure should always be set to true. 
  
 // NOTE: This request contains a single conversion as a demonstration. 
  
 // However, if you have multiple conversions to upload, it's best to upload multiple 
  
 // conversions per request instead of sending a separate request per conversion. 
  
 // See the following for per-request limits: 
  
 // https://developers.google.com/google-ads/api/docs/best-practices/quotas#conversion_upload 
  
 UploadClickConversionsResponse 
  
 response 
  
 = 
  
 conversionUploadService 
 . 
 UploadClickConversions 
 ( 
  
 new 
  
 UploadClickConversionsRequest 
 () 
  
 { 
  
 CustomerId 
  
 = 
  
 customerId 
 . 
 ToString 
 (), 
  
 Conversions 
  
 = 
  
 { 
  
 clickConversion 
  
 }, 
  
 // Enables partial failure (must be true). 
  
 PartialFailure 
  
 = 
  
 true 
  
 }); 
  
 // Prints any partial errors returned. 
  
 // To review the overall health of your recent uploads, see: 
  
 // https://developers.google.com/google-ads/api/docs/conversions/upload-summaries 
  
 if 
  
 ( 
 response 
 . 
 PartialFailureError 
  
 != 
  
 null 
 ) 
  
 { 
  
 // Extracts the partial failure from the response status. 
  
 GoogleAdsFailure 
  
 partialFailure 
  
 = 
  
 response 
 . 
 PartialFailure 
 ; 
  
 Console 
 . 
 WriteLine 
 ( 
 $"{partialFailure.Errors.Count} partial failure error(s) " 
  
 + 
  
 $"occurred" 
 ); 
  
 } 
  
 else 
  
 { 
  
 ClickConversionResult 
  
 result 
  
 = 
  
 response 
 . 
 Results 
 [ 
 0 
 ]; 
  
 // Prints the result. 
  
 Console 
 . 
 WriteLine 
 ( 
 $"Uploaded conversion that occurred at" 
  
 + 
  
 $" {result.ConversionDateTime} to {result.ConversionAction}." 
 ); 
  
 } 
  
 } 
  
 catch 
  
 ( 
 GoogleAdsException 
  
 e 
 ) 
  
 { 
  
 Console 
 . 
 WriteLine 
 ( 
 "Failure:" 
 ); 
  
 Console 
 . 
 WriteLine 
 ( 
 $"Message: {e.Message}" 
 ); 
  
 Console 
 . 
 WriteLine 
 ( 
 $"Failure: {e.Failure}" 
 ); 
  
 Console 
 . 
 WriteLine 
 ( 
 $"Request ID: {e.RequestId}" 
 ); 
  
 throw 
 ; 
  
 } 
  
 } 
  
 /// <summary> 
  
 /// Normalizes the email address and hashes it. For this use case, Google Ads requires 
  
 /// removal of any '.' characters preceding <code>gmail.com</code> or 
  
 /// <code>googlemail.com</code>. 
  
 /// </summary> 
  
 /// <param name="emailAddress">The email address.</param> 
  
 /// <returns>The hash code.</returns> 
  
 private 
  
 string 
  
 NormalizeAndHashEmailAddress 
 ( 
 string 
  
 emailAddress 
 ) 
  
 { 
  
 string 
  
 normalizedEmail 
  
 = 
  
 emailAddress 
 . 
 ToLower 
 (); 
  
 string 
 [] 
  
 emailParts 
  
 = 
  
 normalizedEmail 
 . 
 Split 
 ( 
 '@' 
 ); 
  
 if 
  
 ( 
 emailParts 
 . 
 Length 
 > 
 1 
 && 
 ( 
 emailParts 
 [ 
 1 
 ] 
  
 == 
  
 "gmail.com" 
  
 || 
  
 emailParts 
 [ 
 1 
 ] 
  
 == 
  
 "googlemail.com" 
 )) 
  
 { 
  
 // Removes any '.' characters from the portion of the email address before 
  
 // the domain if the domain is gmail.com or googlemail.com. 
  
 emailParts 
 [ 
 0 
 ] 
  
 = 
  
 emailParts 
 [ 
 0 
 ]. 
 Replace 
 ( 
 "." 
 , 
  
 "" 
 ); 
  
 normalizedEmail 
  
 = 
  
 $"{emailParts[0]}@{emailParts[1]}" 
 ; 
  
 } 
  
 return 
  
 NormalizeAndHash 
 ( 
 normalizedEmail 
 ); 
  
 } 
  
 /// <summary> 
  
 /// Normalizes and hashes a string value. 
  
 /// </summary> 
  
 /// <param name="value">The value to normalize and hash.</param> 
  
 /// <returns>The normalized and hashed value.</returns> 
  
 private 
  
 static 
  
 string 
  
 NormalizeAndHash 
 ( 
 string 
  
 value 
 ) 
  
 { 
  
 return 
  
 ToSha256String 
 ( 
 digest 
 , 
  
 ToNormalizedValue 
 ( 
 value 
 )); 
  
 } 
  
 /// <summary> 
  
 /// Hash a string value using SHA-256 hashing algorithm. 
  
 /// </summary> 
  
 /// <param name="digest">Provides the algorithm for SHA-256.</param> 
  
 /// <param name="value">The string value (e.g. an email address) to hash.</param> 
  
 /// <returns>The hashed value.</returns> 
  
 private 
  
 static 
  
 string 
  
 ToSha256String 
 ( 
 SHA256 
  
 digest 
 , 
  
 string 
  
 value 
 ) 
  
 { 
  
 byte 
 [] 
  
 digestBytes 
  
 = 
  
 digest 
 . 
 ComputeHash 
 ( 
 Encoding 
 . 
 UTF8 
 . 
 GetBytes 
 ( 
 value 
 )); 
  
 // Convert the byte array into an unhyphenated hexadecimal string. 
  
 return 
  
 BitConverter 
 . 
 ToString 
 ( 
 digestBytes 
 ). 
 Replace 
 ( 
 "-" 
 , 
  
 string 
 . 
 Empty 
 ); 
  
 } 
  
 /// <summary> 
  
 /// Removes leading and trailing whitespace and converts all characters to 
  
 /// lower case. 
  
 /// </summary> 
  
 /// <param name="value">The value to normalize.</param> 
  
 /// <returns>The normalized value.</returns> 
  
 private 
  
 static 
  
 string 
  
 ToNormalizedValue 
 ( 
 string 
  
 value 
 ) 
  
 { 
  
 return 
  
 value 
 . 
 Trim 
 (). 
 ToLower 
 (); 
  
 } 
  
 } 
 } 
  
  

PHP

< ?php 
 /** 
 * Copyright 2021 Google LLC 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *     https://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */ 
 namespace Google\Ads\GoogleAds\Examples\Remarketing; 
 require __DIR__ . '/../../vendor/autoload.php'; 
 use GetOpt\GetOpt; 
 use Google\Ads\GoogleAds\Examples\Utils\ArgumentNames; 
 use Google\Ads\GoogleAds\Examples\Utils\ArgumentParser; 
 use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder; 
 use Google\Ads\GoogleAds\Lib\V21\GoogleAdsClient; 
 use Google\Ads\GoogleAds\Lib\V21\GoogleAdsClientBuilder; 
 use Google\Ads\GoogleAds\Lib\V21\GoogleAdsException; 
 use Google\Ads\GoogleAds\Util\V21\ResourceNames; 
 use Google\Ads\GoogleAds\V21\Common\Consent; 
 use Google\Ads\GoogleAds\V21\Common\OfflineUserAddressInfo; 
 use Google\Ads\GoogleAds\V21\Common\UserIdentifier; 
 use Google\Ads\GoogleAds\V21\Enums\ConsentStatusEnum\ConsentStatus; 
 use Google\Ads\GoogleAds\V21\Enums\UserIdentifierSourceEnum\UserIdentifierSource; 
 use Google\Ads\GoogleAds\V21\Errors\GoogleAdsError; 
 use Google\Ads\GoogleAds\V21\Services\ClickConversion; 
 use Google\Ads\GoogleAds\V21\Services\ClickConversionResult; 
 use Google\Ads\GoogleAds\V21\Services\UploadClickConversionsRequest; 
 use Google\Ads\GoogleAds\V21\Services\SessionAttributeKeyValuePair; 
 use Google\Ads\GoogleAds\V21\Services\SessionAttributesKeyValuePairs; 
 use Google\ApiCore\ApiException; 
 /** 
 * Uploads an enhanced conversion for leads by uploading a ClickConversion with hashed, first-party 
 * user-provided data from your website lead forms. This includes user identifiers, and optionally a 
 * click ID and order ID. With this information, Google can tie the conversion to the ad that drove 
 * the lead. 
 */ 
 class UploadEnhancedConversionsForLeads 
 { 
 private const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE'; 
 private const CONVERSION_ACTION_ID = 'INSERT_CONVERSION_ACTION_ID_HERE'; 
 // The date time at which the conversion occurred. 
 // Must be after the click time, and must include the time zone offset. 
 // The format is "yyyy-mm-dd hh:mm:ss+|-hh:mm", e.g. '2019-01-01 12:32:45-08:00'. 
 private const CONVERSION_DATE_TIME = 'INSERT_CONVERSION_DATE_TIME_HERE'; 
 private const CONVERSION_VALUE = 'INSERT_CONVERSION_VALUE_HERE'; 
 // Optional: Specifies the order ID. 
 private const ORDER_ID = null; 
 // Optional: The Google Click ID for which conversions are uploaded. 
 private const GCLID = null; 
 // Optional: The consent status for ad user data. 
 private const AD_USER_DATA_CONSENT = null; 
 // Optional: a str token of encoded session atttributes. Only one of 
 // SESSION_ATTRIBUTES_ENCODED or SESSION_ATTRIBUTES_DICT should be passed. 
 private const SESSION_ATTRIBUTES_ENCODED = null; 
 // Optional: a dict[str, str] of session attribute 
 // key value pairs. Only one of SESSION_ATTRIBUTES_ENCODED or 
 // SESSION_ATTRIBUTES_DICT should be passed. 
 private const SESSION_ATTRIBUTES_DICT = null; 
 public static function main() 
 { 
 // Either pass the required parameters for this example on the command line, or insert them 
 // into the constants above. 
 $options = (new ArgumentParser())->parseCommandArguments([ 
 ArgumentNames::CUSTOMER_ID => GetOpt::REQUIRED_ARGUMENT, 
 ArgumentNames::CONVERSION_ACTION_ID => GetOpt::REQUIRED_ARGUMENT, 
 ArgumentNames::CONVERSION_DATE_TIME => GetOpt::REQUIRED_ARGUMENT, 
 ArgumentNames::CONVERSION_VALUE => GetOpt::REQUIRED_ARGUMENT, 
 ArgumentNames::ORDER_ID => GetOpt::OPTIONAL_ARGUMENT, 
 ArgumentNames::GCLID => GetOpt::OPTIONAL_ARGUMENT, 
 ArgumentNames::AD_USER_DATA_CONSENT => GetOpt::OPTIONAL_ARGUMENT, 
 ArgumentNames::SESSION_ATTRIBUTES_ENCODED => GetOpt::OPTIONAL_ARGUMENT, 
 ArgumentNames::SESSION_ATTRIBUTES_DICT => GetOpt::OPTIONAL_ARGUMENT // e.g. "foo=bar, bcd=xyz" 
 ]); 
 // Parse SESSION_ATTRIBUTES_DICT into an associative array if provided 
 $sessionAttributesDict = []; 
 if (!empty($options[ArgumentNames::SESSION_ATTRIBUTES_DICT])) { 
 $pairs = explode(',', $options[ArgumentNames::SESSION_ATTRIBUTES_DICT]); 
 foreach ($pairs as $pair) { 
 [$key, $value] = explode('=', $pair, 2); 
 $sessionAttributesDict[trim($key)] = trim($value); 
 } 
 } 
 if ( 
 !empty($options[ArgumentNames::SESSION_ATTRIBUTES_ENCODED]) 
&& !empty($options[ArgumentNames::SESSION_ATTRIBUTES_DICT]) 
 ) { 
 throw new \InvalidArgumentException( 
 "Only one of 'session_attributes_encoded' or " . 
 "'session_attributes_dict' can be set." 
 ); 
 } 
 // Generate a refreshable OAuth2 credential for authentication. 
 $oAuth2Credential = (new OAuth2TokenBuilder())->fromFile()->build(); 
 // Construct a Google Ads client configured from a properties file and the 
 // OAuth2 credentials above. 
 $googleAdsClient = (new GoogleAdsClientBuilder()) 
 ->fromFile() 
 ->withOAuth2Credential($oAuth2Credential) 
 ->build(); 
 try { 
 self::runExample( 
 $googleAdsClient, 
 $options[ArgumentNames::CUSTOMER_ID] ?: self::CUSTOMER_ID, 
 $options[ArgumentNames::CONVERSION_ACTION_ID] ?: self::CONVERSION_ACTION_ID, 
 $options[ArgumentNames::CONVERSION_DATE_TIME] ?: self::CONVERSION_DATE_TIME, 
 $options[ArgumentNames::CONVERSION_VALUE] ?: self::CONVERSION_VALUE, 
 $options[ArgumentNames::ORDER_ID] ?: self::ORDER_ID, 
 $options[ArgumentNames::GCLID] ?: self::GCLID, 
 $options[ArgumentNames::AD_USER_DATA_CONSENT] 
 ? ConsentStatus::value($options[ArgumentNames::AD_USER_DATA_CONSENT]) 
 : self::AD_USER_DATA_CONSENT, 
 $options[ArgumentNames::SESSION_ATTRIBUTES_ENCODED] ?: self::SESSION_ATTRIBUTES_ENCODED, 
 // Check to make sure the unnested argument passed of SESSION_ATTRIBUTES_DICT is 
 // not the default empty array. 
 !empty($sessionAttributesDict) ? $sessionAttributesDict : self::SESSION_ATTRIBUTES_DICT 
 ); 
 } catch (GoogleAdsException $googleAdsException) { 
 printf( 
 "Request with ID '%s' has failed.%sGoogle Ads failure details:%s", 
 $googleAdsException->getRequestId(), 
 PHP_EOL, 
 PHP_EOL 
 ); 
 foreach ($googleAdsException->getGoogleAdsFailure()->getErrors() as $error) { 
 /** @var GoogleAdsError $error */ 
 printf( 
 "\t%s: %s%s", 
 $error->getErrorCode()->getErrorCode(), 
 $error->getMessage(), 
 PHP_EOL 
 ); 
 } 
 exit(1); 
 } catch (ApiException $apiException) { 
 printf( 
 "ApiException was thrown with message '%s'.%s", 
 $apiException->getMessage(), 
 PHP_EOL 
 ); 
 exit(1); 
 } 
 } 
 /** 
 * Runs the example. 
 * 
 * @param GoogleAdsClient $googleAdsClient the Google Ads API client 
 * @param int $customerId the customer ID 
 * @param int $conversionActionId the ID of the conversion action associated with this 
 *      conversion 
 * @param string $conversionDateTime the date and time of the conversion 
 *      The format is "yyyy-mm-dd hh:mm:ss+|-hh:mm", e.g. “2019-01-01 12:32:45-08:00” 
 * @param float $conversionValue the value of the conversion 
 * @param string|null $orderId the unique order ID (transaction ID) of the conversion 
 * @param string|null $gclid the Google click ID of the conversion 
 * @param int|null $adUserDataConsent the ad user data consent for the click 
 * @param string|null $sessionAttributesEncoded the str token of encoded session atttributes 
 * @param array<string,string>|null $sessionAttributesDict An associative array of str session attributes tokens 
 */ 
 public static function runExample( 
 GoogleAdsClient $googleAdsClient, 
 int $customerId, 
 int $conversionActionId, 
 string $conversionDateTime, 
 float $conversionValue, 
 ?string $orderId, 
 ?string $gclid, 
 ?int $adUserDataConsent, 
 // Only one of 'sessionAttributesEncoded' or 
 // 'sessionAttributesDict' can be passed at a time. If both are 
 // passed the example will fail with an InvalidArgumentException. 
 ?string $sessionAttributesEncoded, 
 ?array $sessionAttributesDict 
 ) { 
 // Creates a click conversion with the specified attributes. 
 $clickConversion = new ClickConversion(); 
 // Extract user email and phone from the raw data, normalize and hash it, then wrap it in 
 // UserIdentifier objects. Creates a separate UserIdentifier object for each. 
 // The data in this example is hardcoded, but in your application you might read the raw 
 // data from an input file. 
 // IMPORTANT: Since the identifier attribute of UserIdentifier 
 // (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier) is a 
 // oneof 
 // (https://protobuf.dev/programming-guides/proto3/#oneof-features), you must set only ONE 
 // of hashedEmail, hashedPhoneNumber, mobileId, thirdPartyUserId, or addressInfo. Setting 
 // more than one of these attributes on the same UserIdentifier will clear all the other 
 // members of the oneof. For example, the following code is INCORRECT and will result in a 
 // UserIdentifier with ONLY a hashedPhoneNumber. 
 // 
 // $incorrectlyPopulatedUserIdentifier = new UserIdentifier([ 
 //    'hashed_email' => '...', 
 //    'hashed_phone_number' => '...' 
 // ]); 
 $rawRecord = [ 
 // Email address that includes a period (.) before the Gmail domain. 
 'email' => 'alex.2@example.com', 
 // Phone number to be converted to E.164 format, with a leading '+' as required. 
 'phone' => '+1 800 5550102', 
 // This example lets you input conversion details as arguments, but in reality you might 
 // store this data alongside other user data, so we include it in this sample user 
 // record. 
 'orderId' => $orderId, 
 'gclid' => $gclid, 
 'conversionActionId' => $conversionActionId, 
 'conversionDateTime' => $conversionDateTime, 
 'conversionValue' => $conversionValue, 
 'currencyCode' => 'USD', 
 'adUserDataConsent' => $adUserDataConsent, 
 'sessionAttributesEncoded' => $sessionAttributesEncoded, 
 'sessionAttributesDict' => $sessionAttributesDict 
 ]; 
 // Creates a list for the user identifiers. 
 $userIdentifiers = []; 
 // Uses the SHA-256 hash algorithm for hashing user identifiers in a privacy-safe way, as 
 // described at https://support.google.com/google-ads/answer/9888656. 
 $hashAlgorithm = "sha256"; 
 // Creates a user identifier using the hashed email address, using the normalize and hash 
 // method specifically for email addresses. 
 $emailIdentifier = new UserIdentifier([ 
 // Uses the normalize and hash method specifically for email addresses. 
 'hashed_email' => self::normalizeAndHashEmailAddress( 
 $hashAlgorithm, 
 $rawRecord['email'] 
 ), 
 // Optional: Specifies the user identifier source. 
 'user_identifier_source' => UserIdentifierSource::FIRST_PARTY 
 ]); 
 $userIdentifiers[] = $emailIdentifier; 
 // Checks if the record has a phone number, and if so, adds a UserIdentifier for it. 
 if (array_key_exists('phone', $rawRecord)) { 
 $hashedPhoneNumberIdentifier = new UserIdentifier([ 
 'hashed_phone_number' => self::normalizeAndHash( 
 $hashAlgorithm, 
 $rawRecord['phone'], 
 true 
 ) 
 ]); 
 // Adds the hashed email identifier to the user identifiers list. 
 $userIdentifiers[] = $hashedPhoneNumberIdentifier; 
 } 
 // Adds the user identifiers to the conversion. 
 $clickConversion->setUserIdentifiers($userIdentifiers); 
 // Adds details of the conversion. 
 $clickConversion->setConversionAction( 
 ResourceNames::forConversionAction($customerId, $rawRecord['conversionActionId']) 
 ); 
 $clickConversion->setConversionDateTime($rawRecord['conversionDateTime']); 
 $clickConversion->setConversionValue($rawRecord['conversionValue']); 
 $clickConversion->setCurrencyCode($rawRecord['currencyCode']); 
 // Sets the order ID if provided. 
 if (!empty($rawRecord['orderId'])) { 
 $clickConversion->setOrderId($rawRecord['orderId']); 
 } 
 // Sets the Google click ID (gclid) if provided. 
 if (!empty($rawRecord['gclid'])) { 
 $clickConversion->setGclid($rawRecord['gclid']); 
 } 
 // Sets the ad user data consent if provided. 
 if (!empty($rawRecord['adUserDataConsent'])) { 
 // Specifies whether user consent was obtained for the data you are uploading. See 
 // https://www.google.com/about/company/user-consent-policy for details. 
 $clickConversion->setConsent( 
 new Consent(['ad_user_data' => $rawRecord['adUserDataConsent']]) 
 ); 
 } 
 // Set one of the sessionAttributesEncoded or 
 // SessionAttributeKeyValuePair fields if either are provided. 
 if (!empty($sessionAttributesEncoded)) { 
 $clickConversion->setSessionAttributesEncoded($sessionAttributesEncoded); 
 } elseif (!empty($sessionAttributesDict)) { 
 // Create a new container object to hold key-value pairs. 
 $sessionAttributesKeyValuePairs = new SessionAttributesKeyValuePairs(); 
 // Initialize an array to hold individual key-value pair messages. 
 $keyValuePairs = []; 
 // Append each key-value pair provided to the $keyValuePairs array 
 foreach ($sessionAttributesDict as $key => $value) { 
 $pair = new SessionAttributeKeyValuePair(); 
 $pair->setSessionAttributeKey($key); 
 $pair->setSessionAttributeValue($value); 
 $keyValuePairs[] = $pair; 
 } 
 // Set the the full list of key-value pairs on the container object. 
 $sessionAttributesKeyValuePairs->setKeyValuePairs($keyValuePairs); 
 // Attach the container of key-value pairs to the ClickConversion object. 
 $clickConversion->setSessionAttributesKeyValuePairs($sessionAttributesKeyValuePairs); 
 } 
 // Issues a request to upload the click conversion. 
 $conversionUploadServiceClient = $googleAdsClient->getConversionUploadServiceClient(); 
 // NOTE: This request contains a single conversion as a demonstration.  However, if you have 
 // multiple conversions to upload, it's best to upload multiple conversions per request 
 // instead of sending a separate request per conversion. See the following for per-request 
 // limits: 
 // https://developers.google.com/google-ads/api/docs/best-practices/quotas#conversion_upload_service 
 $response = $conversionUploadServiceClient->uploadClickConversions( 
 // Enables partial failure (must be true). 
 UploadClickConversionsRequest::build($customerId, [$clickConversion], true) 
 ); 
 // Prints the status message if any partial failure error is returned. 
 // Note: The details of each partial failure error are not printed here, you can refer to 
 // the example HandlePartialFailure.php to learn more. 
 // To review the overall health of your recent uploads, see: 
 // https://developers.google.com/google-ads/api/docs/conversions/upload-summaries 
 if ($response->hasPartialFailureError()) { 
 printf( 
 "Partial failures occurred: '%s'.%s", 
 $response->getPartialFailureError()->getMessage(), 
 PHP_EOL 
 ); 
 } else { 
 /** @var ClickConversionResult $clickConversionResult */ 
 $clickConversionResult = $response->getResults()[0]; 
 // Only prints valid results. 
 if ($clickConversionResult->hasConversionDateTime()) { 
 printf( 
 "Uploaded conversion that occurred at '%s' to '%s'.%s", 
 $clickConversionResult->getConversionDateTime(), 
 $clickConversionResult->getConversionAction(), 
 PHP_EOL 
 ); 
 } 
 } 
 } 
 /** 
 * Returns the result of normalizing and then hashing the string using the provided hash 
 * algorithm. Private customer data must be hashed during upload, as described at 
 * https://support.google.com/google-ads/answer/7474263. 
 * 
 * @param string $hashAlgorithm the hash algorithm to use 
 * @param string $value the value to normalize and hash 
 * @return string the normalized and hashed value 
 */ 
 private static function normalizeAndHash(string $hashAlgorithm, string $value): string 
 { 
 // Normalizes by first converting all characters to lowercase, then trimming spaces. 
 $normalized = strtolower($value); 
 // Removes leading, trailing, and intermediate spaces. 
 $normalized = str_replace(' ', '', $normalized); 
 return hash($hashAlgorithm, strtolower(trim($normalized))); 
 } 
 /** 
 * Returns the result of normalizing and hashing an email address. For this use case, Google 
 * Ads requires removal of any '.' characters preceding "gmail.com" or "googlemail.com". 
 * 
 * @param string $hashAlgorithm the hash algorithm to use 
 * @param string $emailAddress the email address to normalize and hash 
 * @return string the normalized and hashed email address 
 */ 
 private static function normalizeAndHashEmailAddress( 
 string $hashAlgorithm, 
 string $emailAddress 
 ): string { 
 $normalizedEmail = strtolower($emailAddress); 
 $emailParts = explode("@", $normalizedEmail); 
 if ( 
 count($emailParts) > 1 
 && preg_match('/^(gmail|googlemail)\.com\s*/', $emailParts[1]) 
 ) { 
 // Removes any '.' characters from the portion of the email address before the domain 
 // if the domain is gmail.com or googlemail.com. 
 $emailParts[0] = str_replace(".", "", $emailParts[0]); 
 $normalizedEmail = sprintf('%s@%s', $emailParts[0], $emailParts[1]); 
 } 
 return self::normalizeAndHash($hashAlgorithm, $normalizedEmail); 
 } 
 } 
 UploadEnhancedConversionsForLeads::main(); 
  
  

Python

 #!/usr/bin/env python 
 # Copyright 2021 Google LLC 
 # 
 # Licensed under the Apache License, Version 2.0 (the "License"); 
 # you may not use this file except in compliance with the License. 
 # You may obtain a copy of the License at 
 # 
 #     https://www.apache.org/licenses/LICENSE-2.0 
 # 
 # Unless required by applicable law or agreed to in writing, software 
 # distributed under the License is distributed on an "AS IS" BASIS, 
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 # See the License for the specific language governing permissions and 
 # limitations under the License. 
 """Uploads an enhanced conversion for leads by uploading a ClickConversion. 
 The click conversion has hashed, first-party user-provided data from your 
 website lead forms. This includes user identifiers, and optionally, a click ID 
 and order ID. With this information, Google can tie the conversion to the ad 
 that drove the lead. 
 """ 
 import 
  
 argparse 
 import 
  
 hashlib 
 import 
  
 re 
 import 
  
 sys 
 from 
  
 typing 
  
 import 
 Dict 
 , 
 Optional 
 , 
 Union 
 from 
  
 google.ads.googleads.client 
  
 import 
 GoogleAdsClient 
 from 
  
 google.ads.googleads.errors 
  
 import 
 GoogleAdsException 
 from 
  
 google.ads.googleads.v21.common.types.offline_user_data 
  
 import 
 ( 
 UserIdentifier 
 , 
 ) 
 from 
  
 google.ads.googleads.v21.services.services.conversion_action_service 
  
 import 
 ( 
 ConversionActionServiceClient 
 , 
 ) 
 from 
  
 google.ads.googleads.v21.services.services.conversion_upload_service 
  
 import 
 ( 
 ConversionUploadServiceClient 
 , 
 ) 
 from 
  
 google.ads.googleads.v21.services.types.conversion_upload_service 
  
 import 
 ( 
 ClickConversion 
 , 
 ClickConversionResult 
 , 
 SessionAttributeKeyValuePair 
 , 
 UploadClickConversionsResponse 
 , 
 ) 
 def 
  
 main 
 ( 
 client 
 : 
 GoogleAdsClient 
 , 
 customer_id 
 : 
 str 
 , 
 conversion_action_id 
 : 
 str 
 , 
 conversion_date_time 
 : 
 str 
 , 
 conversion_value 
 : 
 float 
 , 
 order_id 
 : 
 Optional 
 [ 
 str 
 ], 
 gclid 
 : 
 Optional 
 [ 
 str 
 ], 
 ad_user_data_consent 
 : 
 Optional 
 [ 
 str 
 ], 
 session_attributes_encoded 
 : 
 Optional 
 [ 
 str 
 ] 
 = 
 None 
 , 
 session_attributes_dict 
 : 
 Optional 
 [ 
 Dict 
 [ 
 str 
 , 
 str 
 ]] 
 = 
 None 
 , 
 ) 
 - 
> None 
 : 
  
 """The main method that creates all necessary entities for the example. 
 Args: 
 client: An initialized GoogleAdsClient instance. 
 customer_id: The client customer ID string. 
 conversion_action_id: The ID of the conversion action to upload to. 
 conversion_date_time: The date and time of the conversion. 
 conversion_value: The value of the conversion. 
 order_id: The unique ID (transaction ID) of the conversion. 
 gclid: The Google click ID for the click. 
 ad_user_data_consent: The consent status for ad user data for all 
 members in the job. 
 session_attributes_encoded: a str token of encoded session atttributes. 
 Only one of session_attributes_encoded or session_attributes_dict 
 should be passed. 
 session_attributes_dict: a dict[str, str] of session attribute 
 key value pairs. Only one of session_attributes_encoded or 
 session_attributes_dict should be passed. 
 """ 
 if 
 session_attributes_encoded 
 and 
 session_attributes_dict 
 : 
 raise 
 ValueError 
 ( 
 "Only one of 'session_attributes_encoded' or " 
 "'session_attributes_dict' can be set." 
 ) 
 # Extract user email and phone from the raw data, normalize and hash it, 
 # then wrap it in UserIdentifier objects. Create a separate UserIdentifier 
 # object for each. The data in this example is hardcoded, but in your 
 # application you might read the raw data from an input file. 
 # IMPORTANT: Since the identifier attribute of UserIdentifier 
 # (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier) 
 # is a oneof 
 # (https://protobuf.dev/programming-guides/proto3/#oneof-features), you must 
 # set only ONE of hashed_email, hashed_phone_number, mobile_id, 
 # third_party_user_id, or address_info. Setting more than one of these 
 # attributes on the same UserIdentifier will clear all the other members of 
 # the oneof. For example, the following code is INCORRECT and will result in 
 # a UserIdentifier with ONLY a hashed_phone_number: 
 # 
 # incorrectly_populated_user_identifier = client.get_type("UserIdentifier") 
 # incorrectly_populated_user_identifier.hashed_email = "..."" 
 # incorrectly_populated_user_identifier.hashed_phone_number = "..."" 
 raw_record 
 : 
 Dict 
 [ 
 str 
 , 
 Union 
 [ 
 str 
 , 
 float 
 ]] 
 = 
 { 
 # Email address that includes a period (.) before the Gmail domain. 
 "email" 
 : 
 "alex.2@example.com" 
 , 
 # Phone number to be converted to E.164 format, with a leading '+' as 
 # required. 
 "phone" 
 : 
 "+1 800 5550102" 
 , 
 # This example lets you input conversion details as arguments, 
 # but in reality you might store this data alongside other user data, 
 # so we include it in this sample user record. 
 "order_id" 
 : 
 order_id 
 , 
 "gclid" 
 : 
 gclid 
 , 
 "conversion_action_id" 
 : 
 conversion_action_id 
 , 
 "conversion_date_time" 
 : 
 conversion_date_time 
 , 
 "conversion_value" 
 : 
 conversion_value 
 , 
 "currency_code" 
 : 
 "USD" 
 , 
 "ad_user_data_consent" 
 : 
 ad_user_data_consent 
 , 
 } 
 # Constructs the click conversion. 
 click_conversion 
 : 
 ClickConversion 
 = 
 client 
 . 
 get_type 
 ( 
 "ClickConversion" 
 ) 
 # Creates a user identifier using the hashed email address, using the 
 # normalize and hash method specifically for email addresses. 
 email_identifier 
 : 
 UserIdentifier 
 = 
 client 
 . 
 get_type 
 ( 
 "UserIdentifier" 
 ) 
 # Optional: Specifies the user identifier source. 
 email_identifier 
 . 
 user_identifier_source 
 = 
 ( 
 client 
 . 
 enums 
 . 
 UserIdentifierSourceEnum 
 . 
 FIRST_PARTY 
 ) 
 # Uses the normalize and hash method specifically for email addresses. 
 email_identifier 
 . 
 hashed_email 
 = 
 normalize_and_hash_email_address 
 ( 
 raw_record 
 [ 
 "email" 
 ] 
 ) 
 # Adds the user identifier to the conversion. 
 click_conversion 
 . 
 user_identifiers 
 . 
 append 
 ( 
 email_identifier 
 ) 
 # Checks if the record has a phone number, and if so, adds a UserIdentifier 
 # for it. 
 if 
 raw_record 
 . 
 get 
 ( 
 "phone" 
 ) 
 is 
 not 
 None 
 : 
 phone_identifier 
 : 
 UserIdentifier 
 = 
 client 
 . 
 get_type 
 ( 
 "UserIdentifier" 
 ) 
 phone_identifier 
 . 
 hashed_phone_number 
 = 
 normalize_and_hash 
 ( 
 raw_record 
 [ 
 "phone" 
 ] 
 ) 
 # Adds the phone identifier to the conversion adjustment. 
 click_conversion 
 . 
 user_identifiers 
 . 
 append 
 ( 
 phone_identifier 
 ) 
 # Add details of the conversion. 
 # Gets the conversion action resource name. 
 conversion_action_service 
 : 
 ConversionActionServiceClient 
 = 
 ( 
 client 
 . 
 get_service 
 ( 
 "ConversionActionService" 
 ) 
 ) 
 click_conversion 
 . 
 conversion_action 
 = 
 ( 
 conversion_action_service 
 . 
 conversion_action_path 
 ( 
 customer_id 
 , 
 raw_record 
 [ 
 "conversion_action_id" 
 ] 
 ) 
 ) 
 click_conversion 
 . 
 conversion_date_time 
 = 
 raw_record 
 [ 
 "conversion_date_time" 
 ] 
 click_conversion 
 . 
 conversion_value 
 = 
 raw_record 
 [ 
 "conversion_value" 
 ] 
 click_conversion 
 . 
 currency_code 
 = 
 raw_record 
 [ 
 "currency_code" 
 ] 
 # Sets the order ID if provided. 
 if 
 raw_record 
 . 
 get 
 ( 
 "order_id" 
 ): 
 click_conversion 
 . 
 order_id 
 = 
 raw_record 
 [ 
 "order_id" 
 ] 
 # Sets the gclid if provided. 
 if 
 raw_record 
 . 
 get 
 ( 
 "gclid" 
 ): 
 click_conversion 
 . 
 gclid 
 = 
 raw_record 
 [ 
 "gclid" 
 ] 
 # Specifies whether user consent was obtained for the data you are 
 # uploading. For more details, see: 
 # https://www.google.com/about/company/user-consent-policy 
 if 
 raw_record 
 [ 
 "ad_user_data_consent" 
 ]: 
 click_conversion 
 . 
 consent 
 . 
 ad_user_data 
 = 
 client 
 . 
 enums 
 . 
 ConsentStatusEnum 
 [ 
 raw_record 
 [ 
 "ad_user_data_consent" 
 ] 
 ] 
 # Set one of the session_attributes_encoded or 
 # session_attributes_key_value_pairs fields if either are provided. 
 if 
 session_attributes_encoded 
 : 
 click_conversion 
 . 
 session_attributes_encoded 
 = 
 session_attributes_encoded 
 elif 
 session_attributes_dict 
 : 
 for 
 key 
 , 
 value 
 in 
 session_attributes_dict 
 . 
 items 
 (): 
 pair 
 : 
 SessionAttributeKeyValuePair 
 = 
 client 
 . 
 get_type 
 ( 
 "SessionAttributeKeyValuePair" 
 ) 
 pair 
 . 
 session_attribute_key 
 = 
 key 
 pair 
 . 
 session_attribute_value 
 = 
 value 
 click_conversion 
 . 
 session_attributes_key_value_pairs 
 . 
 key_value_pairs 
 . 
 append 
 ( 
 pair 
 ) 
 # Creates the conversion upload service client. 
 conversion_upload_service 
 : 
 ConversionUploadServiceClient 
 = 
 ( 
 client 
 . 
 get_service 
 ( 
 "ConversionUploadService" 
 ) 
 ) 
 # Uploads the click conversion. Partial failure should always be set to 
 # True. 
 # NOTE: This request only uploads a single conversion, but if you have 
 # multiple conversions to upload, it's most efficient to upload them in a 
 # single request. See the following for per-request limits for reference: 
 # https://developers.google.com/google-ads/api/docs/best-practices/quotas#conversion_upload_service 
 response 
 : 
 UploadClickConversionsResponse 
 = 
 ( 
 conversion_upload_service 
 . 
 upload_click_conversions 
 ( 
 customer_id 
 = 
 customer_id 
 , 
 conversions 
 = 
 [ 
 click_conversion 
 ], 
 # Enables partial failure (must be true). 
 partial_failure 
 = 
 True 
 , 
 ) 
 ) 
 # Prints any partial errors returned. 
 # To review the overall health of your recent uploads, see: 
 # https://developers.google.com/google-ads/api/docs/conversions/upload-summaries 
 if 
 response 
 . 
 partial_failure_error 
 : 
 print 
 ( 
 "Partial error encountered: " 
 f 
 " 
 { 
 response 
 . 
 partial_failure_error 
 . 
 message 
 } 
 " 
 ) 
 else 
 : 
 # Prints the result. 
 result 
 : 
 ClickConversionResult 
 = 
 response 
 . 
 results 
 [ 
 0 
 ] 
 print 
 ( 
 "Uploaded conversion that occurred at " 
 f 
 " 
 { 
 result 
 . 
 conversion_date_time 
 } 
 " 
 f 
 "to 
 { 
 result 
 . 
 conversion_action 
 } 
 ." 
 ) 
 def 
  
 normalize_and_hash_email_address 
 ( 
 email_address 
 : 
 str 
 ) 
 - 
> str 
 : 
  
 """Returns the result of normalizing and hashing an email address. 
 For this use case, Google Ads requires removal of any '.' characters 
 preceding "gmail.com" or "googlemail.com" 
 Args: 
 email_address: An email address to normalize. 
 Returns: 
 A normalized (lowercase, removed whitespace) and SHA-265 hashed string. 
 """ 
 normalized_email 
 : 
 str 
 = 
 email_address 
 . 
 strip 
 () 
 . 
 lower 
 () 
 email_parts 
 : 
 list 
 [ 
 str 
 ] 
 = 
 normalized_email 
 . 
 split 
 ( 
 "@" 
 ) 
 # Check that there are at least two segments 
 if 
 len 
 ( 
 email_parts 
 ) 
> 1 
 : 
 # Checks whether the domain of the email address is either "gmail.com" 
 # or "googlemail.com". If this regex does not match then this statement 
 # will evaluate to None. 
 if 
 re 
 . 
 match 
 ( 
 r 
 "^(gmail|googlemail)\.com$" 
 , 
 email_parts 
 [ 
 1 
 ]): 
 # Removes any '.' characters from the portion of the email address 
 # before the domain if the domain is gmail.com or googlemail.com. 
 email_parts 
 [ 
 0 
 ] 
 = 
 email_parts 
 [ 
 0 
 ] 
 . 
 replace 
 ( 
 "." 
 , 
 "" 
 ) 
 normalized_email 
 = 
 "@" 
 . 
 join 
 ( 
 email_parts 
 ) 
 return 
 normalize_and_hash 
 ( 
 normalized_email 
 ) 
 def 
  
 normalize_and_hash 
 ( 
 s 
 : 
 str 
 ) 
 - 
> str 
 : 
  
 """Normalizes and hashes a string with SHA-256. 
 Private customer data must be hashed during upload, as described at: 
 https://support.google.com/google-ads/answer/7474263 
 Args: 
 s: The string to perform this operation on. 
 Returns: 
 A normalized (lowercase, removed whitespace) and SHA-256 hashed string. 
 """ 
 return 
 hashlib 
 . 
 sha256 
 ( 
 s 
 . 
 strip 
 () 
 . 
 lower 
 () 
 . 
 encode 
 ()) 
 . 
 hexdigest 
 () 
 if 
 __name__ 
 == 
 "__main__" 
 : 
 # GoogleAdsClient will read the google-ads.yaml configuration file in the 
 # home directory if none is specified. 
 googleads_client 
 : 
 GoogleAdsClient 
 = 
 GoogleAdsClient 
 . 
 load_from_storage 
 ( 
 version 
 = 
 "v21" 
 ) 
 parser 
 : 
 argparse 
 . 
 ArgumentParser 
 = 
 argparse 
 . 
 ArgumentParser 
 ( 
 description 
 = 
 "Imports offline call conversion values for calls related " 
 "to your ads." 
 ) 
 # The following argument(s) should be provided to run the example. 
 parser 
 . 
 add_argument 
 ( 
 "-c" 
 , 
 "--customer_id" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 True 
 , 
 help 
 = 
 "The Google Ads customer ID." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "-a" 
 , 
 "--conversion_action_id" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 True 
 , 
 help 
 = 
 "The ID of the conversion action to upload to." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "-d" 
 , 
 "--conversion_date_time" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 True 
 , 
 help 
 = 
 "The date time at which the conversion occurred. Must be after " 
 "the click time, and must include the time zone offset.  The format is " 
 "'yyyy-mm-dd hh:mm:ss+|-hh:mm', e.g. '2019-01-01 12:32:45-08:00'." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "-v" 
 , 
 "--conversion_value" 
 , 
 type 
 = 
 float 
 , 
 required 
 = 
 True 
 , 
 help 
 = 
 "The value of the conversion." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "-o" 
 , 
 "--order_id" 
 , 
 type 
 = 
 str 
 , 
 help 
 = 
 "the unique ID (transaction ID) of the conversion." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "-g" 
 , 
 "--gclid" 
 , 
 type 
 = 
 str 
 , 
 help 
 = 
 "the Google click ID (gclid) for the click." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "-n" 
 , 
 "--ad_user_data_consent" 
 , 
 type 
 = 
 str 
 , 
 choices 
 = 
 [ 
 e 
 . 
 name 
 for 
 e 
 in 
 googleads_client 
 . 
 enums 
 . 
 ConsentStatusEnum 
 if 
 e 
 . 
 name 
 not 
 in 
 ( 
 "UNSPECIFIED" 
 , 
 "UNKNOWN" 
 ) 
 ], 
 help 
 = 
 ( 
 "The data consent status for ad user data for all members in " 
 "the job." 
 ), 
 ) 
 # A mutually exclusive group means that session_attributes_encoded and 
 # session_attributes_key_value_pairs cannot be passed in at the same time. 
 session_attributes 
 = 
 parser 
 . 
 add_mutually_exclusive_group 
 () 
 session_attributes 
 . 
 add_argument 
 ( 
 "-e" 
 , 
 "--session_attributes_encoded" 
 , 
 type 
 = 
 str 
 , 
 default 
 = 
 None 
 , 
 help 
 = 
 ( 
 "A session attributes token." 
 ), 
 ) 
 session_attributes 
 . 
 add_argument 
 ( 
 "-k" 
 , 
 "--session_attributes_key_value_pairs" 
 , 
 nargs 
 = 
 "+" 
 , 
 type 
 = 
 str 
 , 
 default 
 = 
 None 
 , 
 help 
 = 
 ( 
 "A space-delimited list of session attribute key value pairs. Each " 
 "pair should be separated by an equal sign, for example: " 
 "'-k gad_campaignid=12345 gad_source=1'" 
 ), 
 ) 
 args 
 : 
 argparse 
 . 
 Namespace 
 = 
 parser 
 . 
 parse_args 
 () 
 session_attributes_dict 
 : 
 Optional 
 [ 
 Dict 
 [ 
 str 
 , 
 str 
 ]] 
 = 
 None 
 if 
 args 
 . 
 session_attributes_key_value_pairs 
 : 
 # Convert the string-based input to a dict 
 session_attributes_dict 
 = 
 dict 
 ( 
 pair 
 . 
 split 
 ( 
 "=" 
 ) 
 for 
 pair 
 in 
 args 
 . 
 session_attributes_key_value_pairs 
 ) 
 try 
 : 
 main 
 ( 
 googleads_client 
 , 
 args 
 . 
 customer_id 
 , 
 args 
 . 
 conversion_action_id 
 , 
 args 
 . 
 conversion_date_time 
 , 
 args 
 . 
 conversion_value 
 , 
 args 
 . 
 order_id 
 , 
 args 
 . 
 gclid 
 , 
 args 
 . 
 ad_user_data_consent 
 , 
 # Only one of 'session_attributes_encoded' or 
 # 'session_attributes_dict' can be passed at a time. If both are 
 # passed the example will fail with a ValueError. 
 session_attributes_encoded 
 = 
 args 
 . 
 session_attributes_encoded 
 , 
 session_attributes_dict 
 = 
 session_attributes_dict 
 , 
 ) 
 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 
 " 
 \t 
 Error with message ' 
 { 
 error 
 . 
 message 
 } 
 '." 
 ) 
 if 
 error 
 . 
 location 
 : 
 for 
 field_path_element 
 in 
 error 
 . 
 location 
 . 
 field_path_elements 
 : 
 print 
 ( 
 f 
 " 
 \t\t 
 On field: 
 { 
 field_path_element 
 . 
 field_name 
 } 
 " 
 ) 
 sys 
 . 
 exit 
 ( 
 1 
 ) 
  

Ruby

 #!/usr/bin/env ruby 
 # Encoding: utf-8 
 # 
 # Copyright 2021 Google LLC 
 # 
 # Licensed under the Apache License, Version 2.0 (the "License"); 
 # you may not use this file except in compliance with the License. 
 # You may obtain a copy of the License at 
 # 
 #     https://www.apache.org/licenses/LICENSE-2.0 
 # 
 # Unless required by applicable law or agreed to in writing, software 
 # distributed under the License is distributed on an "AS IS" BASIS, 
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 # See the License for the specific language governing permissions and 
 # limitations under the License. 
 # 
 # Uploads an enhanced conversion for leads by uploading a ClickConversion. 
 # The click conversion has hashed, first-party user-provided data from your 
 # website lead forms. This includes user identifiers, and optionally, a click ID 
 # and order ID. With this information, Google can tie the conversion to the ad 
 # that drove the lead. 
 require 
  
 'optparse' 
 require 
  
 'google/ads/google_ads' 
 def 
  
 upload_conversion_with_identifiers 
 ( 
  
 customer_id 
 , 
  
 conversion_action_id 
 , 
  
 conversion_date_time 
 , 
  
 conversion_value 
 , 
  
 order_id 
 , 
  
 gclid 
 , 
  
 ad_user_data_consent 
 , 
  
 session_attributes_encoded 
 , 
  
 session_attributes_hash 
 ) 
  
 if 
  
 ( 
 session_attributes_encoded! 
 = 
 nil 
 && 
 session_attributes_hash! 
 = 
 nil 
 ) 
  
 raise 
  
 ArgumentError 
 . 
 new 
 ( 
 "Only one of 'session_attributes_encoded' or 'session_attributes_hash' can be set." 
 ) 
  
 end 
  
 # GoogleAdsClient will read a config file from 
  
 # ENV['HOME']/google_ads_config.rb when called without parameters 
  
 client 
  
 = 
  
 Google 
 :: 
 Ads 
 :: 
 GoogleAds 
 :: 
 GoogleAdsClient 
 . 
 new 
  
 # Extract user email and phone from the raw data, normalize and hash it, 
  
 # then wrap it in UserIdentifier objects. Create a separate UserIdentifier 
  
 # object for each. The data in this example is hardcoded, but in your 
  
 # application you might read the raw data from an input file. 
  
 # IMPORTANT: Since the identifier attribute of UserIdentifier 
  
 # (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier) 
  
 # is a oneof 
  
 # (https://protobuf.dev/programming-guides/proto3/#oneof-features), you must 
  
 # set only ONE of hashed_email, hashed_phone_number, mobile_id, 
  
 # third_party_user_id, or address_info. Setting more than one of these 
  
 # attributes on the same UserIdentifier will clear all the other members of 
  
 # the oneof. For example, the following code is INCORRECT and will result in 
  
 # a UserIdentifier with ONLY a hashed_phone_number: 
  
 # 
  
 # incorrectly_populated_user_identifier.hashed_email = "..."" 
  
 # incorrectly_populated_user_identifier.hashed_phone_number = "..."" 
  
 raw_record 
  
 = 
  
 { 
  
 # Email address that includes a period (.) before the Gmail domain. 
  
 "email" 
  
 = 
>  
 "alex.2@example.com" 
 , 
  
 # Phone number to be converted to E.164 format, with a leading '+' as 
  
 # required. 
  
 "phone" 
  
 = 
>  
 "+1 800 5550102" 
 , 
  
 # This example lets you input conversion details as arguments, 
  
 # but in reality you might store this data alongside other user data, 
  
 # so we include it in this sample user record. 
  
 "order_id" 
  
 = 
>  
 order_id 
 , 
  
 "gclid" 
  
 = 
>  
 gclid 
 , 
  
 "conversion_action_id" 
  
 = 
>  
 conversion_action_id 
 , 
  
 "conversion_date_time" 
  
 = 
>  
 conversion_date_time 
 , 
  
 "conversion_value" 
  
 = 
>  
 conversion_value 
 , 
  
 "currency_code" 
  
 = 
>  
 "USD" 
 , 
  
 "ad_user_data_consent" 
  
 = 
>  
 ad_user_data_consent 
 , 
  
 "session_attributes_encoded" 
  
 = 
>  
 session_attributes_encoded 
 , 
  
 "session_attributes_hash" 
  
 = 
>  
 session_attributes_hash 
  
 } 
  
 click_conversion 
  
 = 
  
 client 
 . 
 resource 
 . 
 click_conversion 
  
 do 
  
 | 
 cc 
 | 
  
 cc 
 . 
 conversion_action 
  
 = 
  
 client 
 . 
 path 
 . 
 conversion_action 
 ( 
 customer_id 
 , 
  
 conversion_action_id 
 ) 
  
 cc 
 . 
 conversion_date_time 
  
 = 
  
 conversion_date_time 
  
 cc 
 . 
 conversion_value 
  
 = 
  
 conversion_value 
 . 
 to_f 
  
 cc 
 . 
 currency_code 
  
 = 
  
 'USD' 
  
 unless 
  
 order_id 
 . 
 nil? 
  
 cc 
 . 
 order_id 
  
 = 
  
 order_id 
  
 end 
  
 unless 
  
 raw_record 
 [ 
 "gclid" 
 ]. 
 nil? 
  
 cc 
 . 
 gclid 
  
 = 
  
 gclid 
  
 end 
  
 # Specifies whether user consent was obtained for the data you are 
  
 # uploading. For more details, see: 
  
 # https://www.google.com/about/company/user-consent-policy 
  
 unless 
  
 raw_record 
 [ 
 "ad_user_data_consent" 
 ]. 
 nil? 
  
 cc 
 . 
 consent 
  
 = 
  
 client 
 . 
 resource 
 . 
 consent 
  
 do 
  
 | 
 c 
 | 
  
 c 
 . 
 ad_user_data 
  
 = 
  
 ad_user_data_consent 
  
 end 
  
 end 
  
 # Set one of the session_attributes_encoded or 
  
 # session_attributes_key_value_pairs fields if either are provided. 
  
 if 
  
 session_attributes_encoded 
  
 != 
  
 nil 
  
 cc 
 . 
 class 
 . 
 module_eval 
  
 { 
  
 attr_accessor 
  
 :session_attributes_encoded 
 } 
  
 cc 
 . 
 session_attributes_encoded 
  
 = 
  
 session_attributes_encoded 
  
 elsif 
  
 session_attributes_hash 
  
 != 
  
 nil 
  
 # Add new attribute to click conversion object 
  
 cc 
 . 
 class 
 . 
 module_eval 
  
 { 
  
 attr_accessor 
  
 :session_attributes_key_value_pairs 
 } 
  
 cc 
 . 
 session_attributes_key_value_pairs 
  
 = 
  
 :: 
 Google 
 :: 
 Ads 
 :: 
 GoogleAds 
 :: 
 V19 
 :: 
 Services 
 :: 
 SessionAttributesKeyValuePairs 
 . 
 new 
  
 # Loop thru inputted session_attributes_hash to populate session_attributes_key_value_pairs 
  
 session_attributes_hash 
 . 
 each 
  
 do 
  
 | 
 key 
 , 
  
 value 
 | 
  
 pair 
  
 = 
  
 :: 
 Google 
 :: 
 Ads 
 :: 
 GoogleAds 
 :: 
 V19 
 :: 
 Services 
 :: 
 SessionAttributeKeyValuePair 
 . 
 new 
  
 pair 
 . 
 session_attribute_key 
  
 = 
  
 key 
  
 pair 
 . 
 session_attribute_value 
  
 = 
  
 value 
  
 cc 
 . 
 session_attributes_key_value_pairs 
 . 
 key_value_pairs 
 << 
 pair 
  
 end 
  
 end 
  
  
 # Creates a user identifier using the hashed email address, using the 
  
 # normalize and hash method specifically for email addresses. 
  
 # If using a phone number, use the normalize_and_hash method instead. 
  
 cc 
 . 
 user_identifiers 
 << 
 client 
 . 
 resource 
 . 
 user_identifier 
  
 do 
  
 | 
 ui 
 | 
  
 ui 
 . 
 hashed_email 
  
 = 
  
 normalize_and_hash_email 
 ( 
 raw_record 
 [ 
 "email" 
 ] 
 ) 
  
 # Optional: Specifies the user identifier source. 
  
 ui 
 . 
 user_identifier_source 
  
 = 
  
 :FIRST_PARTY 
  
 end 
  
 # Checks if the record has a phone number, and if so, adds a UserIdentifier 
  
 # for it. 
  
 unless 
  
 raw_record 
 [ 
 "phone" 
 ]. 
 nil? 
  
 cc 
 . 
 user_identifiers 
 << 
 client 
 . 
 resource 
 . 
 user_identifier 
  
 do 
  
 | 
 ui 
 | 
  
 ui 
 . 
 hashed_phone_number 
  
 = 
  
 normalize_and_hash 
 ( 
 raw_record 
 [ 
 "phone" 
 ] 
 ) 
  
 end 
  
 end 
  
 end 
  
 response 
  
 = 
  
 client 
 . 
 service 
 . 
 conversion_upload 
 . 
 upload_click_conversions 
 ( 
  
 customer_id 
 : 
  
 customer_id 
 , 
  
 conversions 
 : 
  
 [ 
 click_conversion 
 ] 
 , 
  
 # Partial failure must be true. 
  
 partial_failure 
 : 
  
 true 
 , 
  
 ) 
  
 if 
  
 response 
 . 
 partial_failure_error 
  
 puts 
  
 "Partial failure encountered: 
 #{ 
 response 
 . 
 partial_failure_error 
 . 
 message 
 } 
 " 
  
 else 
  
 result 
  
 = 
  
 response 
 . 
 results 
 . 
 first 
  
 puts 
  
 "Uploaded click conversion that happened at 
 #{ 
 result 
 . 
 conversion_date_time 
 } 
 " 
  
 \ 
  
 "to 
 #{ 
 result 
 . 
 conversion_action 
 } 
 ." 
  
 end 
 end 
 # Returns the result of normalizing and then hashing the string using the 
 # provided digest.  Private customer data must be hashed during upload, as 
 # described at https://support.google.com/google-ads/answer/7474263. 
 def 
  
 normalize_and_hash 
 ( 
 str 
 ) 
  
 # Remove leading and trailing whitespace and ensure all letters are lowercase 
  
 # before hashing. 
  
 Digest 
 :: 
 SHA256 
 . 
 hexdigest 
 ( 
 str 
 . 
 strip 
 . 
 downcase 
 ) 
 end 
 # Returns the result of normalizing and hashing an email address. For this use 
 # case, Google Ads requires removal of any '.' characters preceding 'gmail.com' 
 # or 'googlemail.com'. 
 def 
  
 normalize_and_hash_email 
 ( 
 email 
 ) 
  
 email_parts 
  
 = 
  
 email 
 . 
 downcase 
 . 
 split 
 ( 
 "@" 
 ) 
  
 # Removes any '.' characters from the portion of the email address before the 
  
 # domain if the domain is gmail.com or googlemail.com. 
  
 if 
  
 email_parts 
 . 
 last 
  
 =~ 
 /^(gmail|googlemail)\.com\s*/ 
  
 email_parts 
 [ 
 0 
 ] 
  
 = 
  
 email_parts 
 [ 
 0 
 ]. 
 gsub 
 ( 
 '.' 
 , 
  
 '' 
 ) 
  
 end 
  
 normalize_and_hash 
 ( 
 email_parts 
 . 
 join 
 ( 
 '@' 
 )) 
 end 
 if 
  
 __FILE__ 
  
 == 
  
 $0 
  
 options 
  
 = 
  
 {} 
  
 # The following parameter(s) should be provided to run the example. You can 
  
 # either specify these by changing the INSERT_XXX_ID_HERE values below, or on 
  
 # the command line. 
  
 # 
  
 # Parameters passed on the command line will override any parameters set in 
  
 # code. 
  
 # 
  
 # Running the example with -h will print the command line usage. 
  
 options 
 [ 
 :customer_id 
 ] 
  
 = 
  
 'INSERT_CUSTOMER_ID_HERE' 
  
 options 
 [ 
 :conversion_action_id 
 ] 
  
 = 
  
 'INSERT_CONVERSION_ACTION_ID_HERE' 
  
 options 
 [ 
 :email_address 
 ] 
  
 = 
  
 'INSERT_EMAIL_ADDRESS_HERE' 
  
 options 
 [ 
 :conversion_date_time 
 ] 
  
 = 
  
 'INSERT_CONVERSION_DATE_TIME_HERE' 
  
 options 
 [ 
 :conversion_value 
 ] 
  
 = 
  
 'INSERT_CONVERSION_VALUE_HERE' 
  
 options 
 [ 
 :order_id 
 ] 
  
 = 
  
 nil 
  
 options 
 [ 
 :gclid 
 ] 
  
 = 
  
 nil 
  
 options 
 [ 
 :ad_user_data_consent 
 ] 
  
 = 
  
 nil 
  
 options 
 [ 
 :session_attributes_encoded 
 ] 
  
 = 
  
 nil 
  
 options 
 [ 
 :session_attributes_hash 
 ] 
  
 = 
  
 nil 
  
 OptionParser 
 . 
 new 
  
 do 
  
 | 
 opts 
 | 
  
 opts 
 . 
 banner 
  
 = 
  
 sprintf 
 ( 
 'Usage: %s [options]' 
 , 
  
 File 
 . 
 basename 
 ( 
 __FILE__ 
 )) 
  
 opts 
 . 
 separator 
  
 '' 
  
 opts 
 . 
 separator 
  
 'Options:' 
  
 opts 
 . 
 on 
 ( 
 '-C' 
 , 
  
 '--customer-id CUSTOMER-ID' 
 , 
  
 String 
 , 
  
 'Customer ID' 
 ) 
  
 do 
  
 | 
 v 
 | 
  
 options 
 [ 
 :customer_id 
 ] 
  
 = 
  
 v 
  
 end 
  
 opts 
 . 
 on 
 ( 
 '-c' 
 , 
  
 '--conversion-action-id CONVERSION-ACTION-ID' 
 , 
  
 String 
 , 
  
 'Conversion Action ID' 
 ) 
  
 do 
  
 | 
 v 
 | 
  
 options 
 [ 
 :conversion_action_id 
 ] 
  
 = 
  
 v 
  
 end 
  
 opts 
 . 
 on 
 ( 
 '-t' 
 , 
  
 '--conversion-date-time CONVERSION-DATE-TIME' 
 , 
  
 String 
 , 
  
 'The date and time of the conversion (should be after click time). ' 
  
 \ 
  
 'The format is "yyyy-mm-dd hh:mm:ss+|-hh:mm", ' 
  
 \ 
  
 'for example: “2019-01-01 12:32:45-08:00”' 
 ) 
  
 do 
  
 | 
 v 
 | 
  
 options 
 [ 
 :conversion_date_time 
 ] 
  
 = 
  
 v 
  
 end 
  
 opts 
 . 
 on 
 ( 
 '-v' 
 , 
  
 '--conversion-value CONVERSION-VALUE' 
 , 
  
 String 
 , 
  
 'Conversion Value' 
 ) 
  
 do 
  
 | 
 v 
 | 
  
 options 
 [ 
 :conversion_value 
 ] 
  
 = 
  
 v 
  
 end 
  
 opts 
 . 
 on 
 ( 
 '-o' 
 , 
  
 '--order-id ORDER-ID' 
 , 
  
 String 
 , 
  
 'Order ID (optional)' 
 ) 
  
 do 
  
 | 
 v 
 | 
  
 options 
 [ 
 :order_id 
 ] 
  
 = 
  
 v 
  
 end 
  
 opts 
 . 
 on 
 ( 
 '-g' 
 , 
  
 '--gclid GCLID' 
 , 
  
 String 
 , 
  
 'The Google click ID (gclid) for the click' 
 ) 
  
 do 
  
 | 
 v 
 | 
  
 options 
 [ 
 :gclid 
 ] 
  
 = 
  
 v 
  
 end 
  
 opts 
 . 
 on 
 ( 
 '-n' 
 , 
  
 '--ad-user-data-consent GCLID' 
 , 
  
 String 
 , 
  
 'The data consent status for ad user data for all members in' 
  
 \ 
  
 'the job.' 
  
 \ 
  
 'e.g. UNKNOWN, GRANTED, DENIED' 
 ) 
  
 do 
  
 | 
 v 
 | 
  
 options 
 [ 
 :ad_user_data_consent 
 ] 
  
 = 
  
 v 
  
 end 
  
 opts 
 . 
 on 
 ( 
 '-e' 
 , 
  
 '--session_attributes_encoded ENCODED-SESSION-ATTRIBUTES' 
 , 
  
 String 
 , 
 'A session attributes token.' 
 ) 
  
 do 
  
 | 
 v 
 | 
  
 options 
 [ 
 :session_attributes_encoded 
 ] 
  
 = 
  
 v 
  
 end 
  
 opts 
 . 
 on 
 ( 
 '-k' 
 , 
  
 '--session_attributes_hash ' 
 , 
  
 String 
 , 
 'A comma-delimited list of session attribute' 
  
 \ 
  
 'key value pairs. Each pair should be separated by an equal sign, for example:' 
  
 \ 
  
 '-k gad_campaignid=12345,gad_source=1' 
 ) 
  
 do 
  
 | 
 v 
 | 
  
 options 
 [ 
 :session_attributes_hash 
 ] 
  
 = 
  
 v 
  
 end 
  
 opts 
 . 
 separator 
  
 '' 
  
 opts 
 . 
 separator 
  
 'Help:' 
  
 opts 
 . 
 on_tail 
 ( 
 '-h' 
 , 
  
 '--help' 
 , 
  
 'Show this message' 
 ) 
  
 do 
  
 puts 
  
 opts 
  
 exit 
  
 end 
  
 end 
 . 
 parse! 
  
 if 
  
 ! 
 options 
 [ 
 :session_attributes_encoded 
 ]. 
 nil? 
 && 
 ! 
 options 
 [ 
 :session_attributes_hash 
 ]. 
 nil? 
  
 raise 
  
 ArgumentError 
 . 
 new 
 ( 
 "Only one of 'session_attributes_encoded' or 'session_attributes_hash' can be set." 
 ) 
  
 end 
  
 if 
  
 options 
 [ 
 :session_attributes_hash 
 ] 
  
 # Convert the string-based input to a hash 
  
 session_attributes_hash 
  
 = 
  
 Hash 
 [ 
 options 
 [ 
 :session_attributes_hash 
 ]. 
 split 
 ( 
 "," 
 ) 
 . 
 map 
  
 { 
  
 | 
 pair 
 | 
  
 pair 
 . 
 split 
 ( 
 "=" 
 ) 
  
 } 
 ] 
  
 end 
  
 begin 
  
 upload_conversion_with_identifiers 
 ( 
  
 options 
 . 
 fetch 
 ( 
 :customer_id 
 ) 
 . 
 tr 
 ( 
 "-" 
 , 
  
 "" 
 ), 
  
 options 
 . 
 fetch 
 ( 
 :conversion_action_id 
 ), 
  
 options 
 . 
 fetch 
 ( 
 :conversion_date_time 
 ), 
  
 options 
 . 
 fetch 
 ( 
 :conversion_value 
 ), 
  
 options 
 [ 
 :order_id 
 ] 
 , 
  
 options 
 [ 
 :gclid 
 ] 
 , 
  
 options 
 [ 
 :ad_user_data_consent 
 ] 
 , 
  
 # Only one of 'session_attributes_encoded' or 
  
 # 'session_attributes_hash' can be passed at a time. If both are 
  
 # passed the example will fail with a ArgumentError. 
  
 options 
 [ 
 :session_attributes_encoded 
 ] 
 , 
  
 session_attributes_hash 
  
 ) 
  
 rescue 
  
 Google 
 :: 
 Ads 
 :: 
 GoogleAds 
 :: 
 Errors 
 :: 
 GoogleAdsError 
  
 = 
>  
 e 
  
 e 
 . 
 failure 
 . 
 errors 
 . 
 each 
  
 do 
  
 | 
 error 
 | 
  
 STDERR 
 . 
 printf 
 ( 
 "Error with message: %s 
 \n 
 " 
 , 
  
 error 
 . 
 message 
 ) 
  
 if 
  
 error 
 . 
 location 
  
 error 
 . 
 location 
 . 
 field_path_elements 
 . 
 each 
  
 do 
  
 | 
 field_path_element 
 | 
  
 STDERR 
 . 
 printf 
 ( 
 " 
 \t 
 On field: %s 
 \n 
 " 
 , 
  
 field_path_element 
 . 
 field_name 
 ) 
  
 end 
  
 end 
  
 error 
 . 
 error_code 
 . 
 to_h 
 . 
 each 
  
 do 
  
 | 
 k 
 , 
  
 v 
 | 
  
 next 
  
 if 
  
 v 
  
 == 
  
 :UNSPECIFIED 
  
 STDERR 
 . 
 printf 
 ( 
 " 
 \t 
 Type: %s 
 \n\t 
 Code: %s 
 \n 
 " 
 , 
  
 k 
 , 
  
 v 
 ) 
  
 end 
  
 end 
  
 end 
 end 
  
  

Perl

 #!/usr/bin/perl -w 
 # 
 # Copyright 2021, Google LLC 
 # 
 # Licensed under the Apache License, Version 2.0 (the "License"); 
 # you may not use this file except in compliance with the License. 
 # You may obtain a copy of the License at 
 # 
 #     http://www.apache.org/licenses/LICENSE-2.0 
 # 
 # Unless required by applicable law or agreed to in writing, software 
 # distributed under the License is distributed on an "AS IS" BASIS, 
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 # See the License for the specific language governing permissions and 
 # limitations under the License. 
 # 
 # Uploads an enhanced conversion for leads by uploading a ClickConversion 
 # with hashed, first-party user-provided data from your website lead forms. 
 # This includes user identifiers, and optionally a click ID and order ID. 
 # With this information, Google can tie the conversion to the ad that drove 
 # the lead. 
 use 
  
 strict 
 ; 
 use 
  
 warnings 
 ; 
 use 
  
 utf8 
 ; 
 use 
  
 FindBin 
  
 qw($Bin) 
 ; 
 use 
  
 lib 
  
 "$Bin/../../lib" 
 ; 
 use 
  
 Google::Ads::GoogleAds::Client 
 ; 
 use 
  
 Google::Ads::GoogleAds::Utils::GoogleAdsHelper 
 ; 
 use 
  
 Google::Ads::GoogleAds::V21::Common::Consent 
 ; 
 use 
  
 Google::Ads::GoogleAds::V21::Common::UserIdentifier 
 ; 
 use 
  
 Google::Ads::GoogleAds::V21::Enums::UserIdentifierSourceEnum 
  
 qw(FIRST_PARTY) 
 ; 
 use 
  
 Google::Ads::GoogleAds::V21::Services::ConversionUploadService::ClickConversion 
 ; 
 use 
  
 Google::Ads::GoogleAds::V21::Services::ConversionUploadService::SessionAttributeKeyValuePair 
 ; 
 use 
  
 Google::Ads::GoogleAds::V21::Utils::ResourceNames 
 ; 
 use 
  
 Getopt::Long 
  
 qw(:config auto_help) 
 ; 
 use 
  
 Pod::Usage 
 ; 
 use 
  
 Cwd 
  
 qw(abs_path) 
 ; 
 use 
  
 Digest::SHA 
  
 qw(sha256_hex) 
 ; 
 sub 
  
 upload_enhanced_conversions_for_leads 
  
 { 
  
 my 
  
 ( 
  
 $api_client 
 , 
  
 $customer_id 
 , 
  
 $conversion_action_id 
 , 
  
 $conversion_date_time 
 , 
  
 $conversion_value 
 , 
  
 $order_id 
 , 
  
 $gclid 
 , 
  
 $ad_user_data_consent 
 , 
  
 $session_attributes_encoded 
 , 
  
 $session_attributes_hash 
  
 ) 
  
 = 
  
 @_ 
 ; 
  
 # Create an empty click conversion. 
  
 my 
  
 $click_conversion 
  
 = 
  
 Google::Ads::GoogleAds::V21::Services::ConversionUploadService:: 
 ClickConversion 
  
 - 
> new 
 ({}); 
  
 # Extract user email and phone from the raw data, normalize and hash it, 
  
 # then wrap it in UserIdentifier objects. Create a separate UserIdentifier 
  
 # object for each. 
  
 # The data in this example is hardcoded, but in your application 
  
 # you might read the raw data from an input file. 
  
 # 
  
 # IMPORTANT: Since the identifier attribute of UserIdentifier 
  
 # (https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier) 
  
 # is a oneof 
  
 # (https://protobuf.dev/programming-guides/proto3/#oneof-features), you must set 
  
 # only ONE of hashed_email, hashed_phone_number, mobile_id, third_party_user_id, 
  
 # or address-info. Setting more than one of these attributes on the same UserIdentifier 
  
 # will clear all the other members of the oneof. For example, the following code is 
  
 # INCORRECT and will result in a UserIdentifier with ONLY a hashed_phone_number: 
  
 # 
  
 # my $incorrect_user_identifier = Google::Ads::GoogleAds::V21::Common::UserIdentifier->new({ 
  
 #   hashedEmail => '...', 
  
 #   hashedPhoneNumber => '...', 
  
 # }); 
  
 my 
  
 $raw_record 
  
 = 
  
 { 
  
 # Email address that includes a period (.) before the Gmail domain. 
  
 email 
  
 = 
>  
 'alex.2@example.com' 
 , 
  
 # Phone number to be converted to E.164 format, with a leading '+' as 
  
 # required. 
  
 phone 
  
 = 
>  
 '+1 800 5550102' 
 , 
  
 # This example lets you input conversion details as arguments, 
  
 # but in reality you might store this data alongside other user data, 
  
 # so we include it in this sample user record. 
  
 orderId 
  
 = 
>  
 $order_id 
 , 
  
 gclid 
  
 = 
>  
 $gclid 
 , 
  
 conversionActionId 
  
 = 
>  
 $conversion_action_id 
 , 
  
 conversionDateTime 
  
 = 
>  
 $conversion_date_time 
 , 
  
 conversionValue 
  
 = 
>  
 $conversion_value 
 , 
  
 currencyCode 
  
 = 
>  
 "USD" 
 , 
  
 adUserDataConsent 
  
 = 
>  
 $ad_user_data_consent 
  
 }; 
  
 my 
  
 $user_identifiers 
  
 = 
  
 [] 
 ; 
  
 # Create a user identifier using the hashed email address, using the normalize 
  
 # and hash method specifically for email addresses. 
  
 my 
  
 $hashed_email 
  
 = 
  
 normalize_and_hash_email_address 
 ( 
 $raw_record 
 - 
> { 
 email 
 }); 
  
 push 
 ( 
  
 @$user_identifiers 
 , 
  
 Google::Ads::GoogleAds::V21::Common:: 
 UserIdentifier 
 - 
> new 
 ({ 
  
 hashedEmail 
  
 = 
>  
 $hashed_email 
 , 
  
 # Optional: Specify the user identifier source. 
  
 userIdentifierSource 
  
 = 
>  
 FIRST_PARTY 
  
 })); 
  
 # Create a user identifier using normalized and hashed phone info. 
  
 my 
  
 $hashed_phone 
  
 = 
  
 normalize_and_hash 
 ( 
 $raw_record 
 - 
> { 
 phone 
 }); 
  
 push 
 ( 
  
 @$user_identifiers 
 , 
  
 Google::Ads::GoogleAds::V21::Common:: 
 UserIdentifier 
 - 
> new 
 ({ 
  
 hashedPhone 
  
 = 
>  
 $hashed_phone 
 , 
  
 # Optional: Specify the user identifier source. 
  
 userIdentifierSource 
  
 = 
>  
 FIRST_PARTY 
  
 })); 
  
 # Add the user identifiers to the conversion. 
  
 $click_conversion 
 - 
> { 
 userIdentifiers 
 } 
  
 = 
  
 $user_identifiers 
 ; 
  
 # Add details of the conversion. 
  
 $click_conversion 
 - 
> { 
 conversionAction 
 } 
  
 = 
  
 Google::Ads::GoogleAds::V21::Utils::ResourceNames:: 
 conversion_action 
 ( 
  
 $customer_id 
 , 
  
 $raw_record 
 - 
> { 
 conversionActionId 
 }); 
  
 $click_conversion 
 - 
> { 
 conversionDateTime 
 } 
  
 = 
  
 $raw_record 
 - 
> { 
 conversionDateTime 
 }; 
  
 $click_conversion 
 - 
> { 
 conversionValue 
 } 
  
 = 
  
 $raw_record 
 - 
> { 
 conversionValue 
 }; 
  
 $click_conversion 
 - 
> { 
 currencyCode 
 } 
  
 = 
  
 $raw_record 
 - 
> { 
 currencyCode 
 }; 
  
 # Set the order ID if provided. 
  
 if 
  
 ( 
 defined 
  
 $raw_record 
 - 
> { 
 orderId 
 }) 
  
 { 
  
 $click_conversion 
 - 
> { 
 orderId 
 } 
  
 = 
  
 $raw_record 
 - 
> { 
 orderId 
 }; 
  
 } 
  
 # Set the Google click ID (gclid) if provided. 
  
 if 
  
 ( 
 defined 
  
 $raw_record 
 - 
> { 
 gclid 
 }) 
  
 { 
  
 $click_conversion 
 - 
> { 
 gclid 
 } 
  
 = 
  
 $raw_record 
 - 
> { 
 gclid 
 }; 
  
 } 
  
 # Set the consent information, if provided. 
  
 if 
  
 ( 
 defined 
  
 $raw_record 
 - 
> { 
 adUserDataConsent 
 }) 
  
 { 
  
 $click_conversion 
 - 
> { 
 consent 
 } 
  
 = 
  
 Google::Ads::GoogleAds::V21::Common:: 
 Consent 
 - 
> new 
 ({ 
  
 adUserData 
  
 = 
>  
 $raw_record 
 - 
> { 
 adUserDataConsent 
 }}); 
  
 } 
  
 # Set one of the session_attributes_encoded or session_attributes_key_value_pairs 
  
 # fields if either are provided. 
  
 if 
  
 ( 
 defined 
  
 $session_attributes_encoded 
 ) 
  
 { 
  
 $click_conversion 
 - 
> { 
 sessionAttributesEncoded 
 } 
  
 = 
  
 $session_attributes_encoded 
 ; 
  
 } 
  
 elsif 
  
 ( 
 defined 
  
 $session_attributes_hash 
 ) 
  
 { 
  
 while 
  
 ( 
 my 
  
 ( 
 $key 
 , 
  
 $value 
 ) 
  
 = 
  
 each 
  
 %$session_attributes_hash 
 ) 
  
 { 
  
 my 
  
 $pair 
  
 = 
  
 Google::Ads::GoogleAds::V21::Services::ConversionUploadService:: 
 SessionAttributeKeyValuePair 
  
 - 
> new 
 ({ 
 sessionAttributeKey 
  
 = 
>  
 $key 
 , 
  
 sessionAttributeValue 
  
 = 
>  
 $value 
 }); 
  
 push 
  
 @ 
 { 
 $click_conversion 
 - 
> { 
 sessionAttributesKeyValuePairs 
 }{ 
 keyValuePairs 
 } 
  
 }, 
  
 $pair 
 ; 
  
 } 
  
 } 
  
 # Upload the click conversion. Partial failure should always be set to true. 
  
 # 
  
 # NOTE: This request contains a single conversion as a demonstration. 
  
 # However, if you have multiple conversions to upload, it's best to 
  
 # upload multiple conversions per request instead of sending a separate 
  
 # request per conversion. See the following for per-request limits: 
  
 # https://developers.google.com/google-ads/api/docs/best-practices/quotas#conversion_upload_service 
  
 my 
  
 $response 
  
 = 
  
 $api_client 
 - 
> ConversionUploadService 
 () 
 - 
> upload_click_conversions 
 ({ 
  
 customerId 
  
 = 
>  
 $customer_id 
 , 
  
 conversions 
  
 = 
>  
 [ 
 $click_conversion 
 ], 
  
 # Enable partial failure (must be true). 
  
 partialFailure 
  
 = 
>  
 "true" 
  
 }); 
  
 # Print any partial errors returned. 
  
 # To review the overall health of your recent uploads, see: 
  
 # https://developers.google.com/google-ads/api/docs/conversions/upload-summaries 
  
 if 
  
 ( 
 $response 
 - 
> { 
 partialFailureError 
 }) 
  
 { 
  
 printf 
  
 "Partial error encountered: '%s'.\n" 
 , 
  
 $response 
 - 
> { 
 partialFailureError 
 }{ 
 message 
 }; 
  
 } 
  
 # Print the result. 
  
 my 
  
 $result 
  
 = 
  
 $response 
 - 
> { 
 results 
 }[ 
 0 
 ]; 
  
 # Only print valid results. 
  
 if 
  
 ( 
 defined 
  
 $result 
 - 
> { 
 conversionDateTime 
 }) 
  
 { 
  
 printf 
  
 "Uploaded conversion that occurred at '%s' to '%s'.\n" 
 , 
  
 $result 
 - 
> { 
 conversionDateTime 
 }, 
  
 $result 
 - 
> { 
 conversionAction 
 }; 
  
 } 
  
 return 
  
 1 
 ; 
 } 
 # Normalizes and hashes a string value. 
 # Private customer data must be hashed during upload, as described at 
 # https://support.google.com/google-ads/answer/7474263. 
 sub 
  
 normalize_and_hash 
  
 { 
  
 my 
  
 $value 
  
 = 
  
 shift 
 ; 
  
 # Removes leading, trailing, and intermediate spaces. 
  
 $value 
  
 =~ 
  
 s/\s+//g 
 ; 
  
 return 
  
 sha256_hex 
 ( 
 lc 
  
 $value 
 ); 
 } 
 # Returns the result of normalizing and hashing an email address. For this use 
 # case, Google Ads requires removal of any '.' characters preceding 'gmail.com' 
 # or 'googlemail.com'. 
 sub 
  
 normalize_and_hash_email_address 
  
 { 
  
 my 
  
 $email_address 
  
 = 
  
 shift 
 ; 
  
 my 
  
 $normalized_email 
  
 = 
  
 lc 
  
 $email_address 
 ; 
  
 my 
  
 @email_parts 
  
 = 
  
 split 
 ( 
 '@' 
 , 
  
 $normalized_email 
 ); 
  
 if 
  
 ( 
 scalar 
  
 @email_parts 
 > 
 1 
 && 
 $email_parts 
 [ 
 1 
 ] 
  
 =~ 
 /^(gmail|googlemail)\.com\s*/ 
 ) 
  
 { 
  
 # Remove any '.' characters from the portion of the email address before the 
  
 # domain if the domain is 'gmail.com' or 'googlemail.com'. 
  
 $email_parts 
 [ 
 0 
 ] 
  
 =~ 
  
 s/\.//g 
 ; 
  
 $normalized_email 
  
 = 
  
 sprintf 
  
 '%s@%s' 
 , 
  
 $email_parts 
 [ 
 0 
 ], 
  
 $email_parts 
 [ 
 1 
 ]; 
  
 } 
  
 return 
  
 normalize_and_hash 
 ( 
 $normalized_email 
 ); 
 } 
 # Don't run the example if the file is being included. 
 if 
  
 ( 
 abs_path 
 ( 
 $0 
 ) 
  
 ne 
  
 abs_path 
 ( 
 __FILE__ 
 )) 
  
 { 
  
 return 
  
 1 
 ; 
 } 
 # Get Google Ads Client, credentials will be read from ~/googleads.properties. 
 my 
  
 $api_client 
  
 = 
  
 Google::Ads::GoogleAds:: 
 Client 
 - 
> new 
 (); 
 # By default examples are set to die on any server returned fault. 
 $api_client 
 - 
> set_die_on_faults 
 ( 
 1 
 ); 
 my 
  
 $customer_id 
 ; 
 my 
  
 $conversion_action_id 
 ; 
 my 
  
 $conversion_date_time 
 ; 
 my 
  
 $conversion_value 
 ; 
 my 
  
 $order_id 
 ; 
 my 
  
 $gclid 
 ; 
 my 
  
 $ad_user_data_consent 
 ; 
 my 
  
 $session_attributes_encoded 
 ; 
 my 
  
 $session_attributes_hash 
 ; 
 my 
  
 $session_attributes_key_value_pairs 
 ; 
 # Parameters passed on the command line will override any parameters set in code. 
 GetOptions 
 ( 
  
 "customer_id=s" 
  
 = 
>  
 \ 
 $customer_id 
 , 
  
 "conversion_action_id=i" 
  
 = 
>  
 \ 
 $conversion_action_id 
 , 
  
 "conversion_date_time=s" 
  
 = 
>  
 \ 
 $conversion_date_time 
 , 
  
 "conversion_value=f" 
  
 = 
>  
 \ 
 $conversion_value 
 , 
  
 "order_id=s" 
  
 = 
>  
 \ 
 $order_id 
 , 
  
 "gclid=s" 
  
 = 
>  
 \ 
 $gclid 
 , 
  
 "ad_user_data_consent=s" 
  
 = 
>  
 \ 
 $ad_user_data_consent 
 , 
  
 "session_attributes_encoded=s" 
  
 = 
>  
 \ 
 $session_attributes_encoded 
 , 
  
 "session_attributes_key_value_pairs=s" 
  
 = 
>  
 \ 
 $session_attributes_key_value_pairs 
 , 
 ); 
 # Print the help message if the parameters are not initialized in the code nor 
 # in the command line. 
 pod2usage 
 ( 
 2 
 ) 
  
 if 
  
 not 
  
 check_params 
 ( 
  
 $customer_id 
 , 
  
 $conversion_action_id 
 , 
  
 $conversion_date_time 
 , 
  
 $conversion_value 
  
 ); 
 if 
  
 ( 
  
 defined 
  
 $session_attributes_encoded 
 && 
 defined 
  
 $session_attributes_key_value_pairs 
 ) 
 { 
  
 die 
 "session_attributes_encoded and session_attributes_key_value_pairs cannot be passed in at the same time." 
 ; 
 } 
 # Convert session_attributes_key_value_pairs to a hash. 
 foreach 
  
 my 
  
 $key_value_pair 
  
 ( 
 split 
  
 ' ' 
 , 
  
 $session_attributes_key_value_pairs 
 ) 
  
 { 
  
 my 
  
 ( 
 $key 
 , 
  
 $value 
 ) 
  
 = 
  
 split 
 ( 
 '=' 
 , 
  
 $key_value_pair 
 ); 
  
 $session_attributes_hash 
 - 
> { 
 $key 
 } 
  
 = 
  
 $value 
 ; 
 } 
 # Call the example. 
 upload_enhanced_conversions_for_leads 
 ( 
  
 $api_client 
 , 
  
 $customer_id 
  
 =~ 
  
 s/-//g 
 r 
 , 
  
 $conversion_action_id 
 , 
  
 $conversion_date_time 
 , 
  
 $conversion_value 
 , 
  
 $order_id 
 , 
  
 $gclid 
 , 
  
 $ad_user_data_consent 
 , 
  
 $session_attributes_encoded 
 , 
  
 $session_attributes_hash 
 ); 
 =pod 
 =head1 NAME 
 upload_enhanced_conversions_for_leads 
 =head1 DESCRIPTION 
 Uploads an enhanced conversion for leads by uploading a ClickConversion 
 with hashed, first-party user-provided data from your website lead forms. 
 =head1 SYNOPSIS 
 upload_enhanced_conversions_for_leads.pl [options] 
 -help                       Show the help message. 
 -customer_id                The Google Ads customer ID. 
 -conversion_action_id       The conversion action ID associated with this conversion. 
 -conversion_date_time       The date time at which the conversion occurred. 
 Must be after the click time, and must include the time zone offset. 
 The format is 'yyyy-mm-dd hh:mm:ss+|-hh:mm', e.g. '2019-01-01 12:32:45-08:00'. 
 -conversion_value           The value of the conversion. 
 -order_id                   [optional] The unique ID (transaction ID) of the conversion. We recommend including if available. 
 -gclid                      [optional] The Google click ID associated with the conversion. We recommend including if available. 
 -ad_user_data_consent		[optional] The ad user data consent for the click. 
 -session_attributes_encoded     [optional] 
 -session_attributes_key_value_pairs [optional] A space-delimited list of session attribute key value pairs. Each pair should be separated by an equal sign, for example: "gad_campaignid=12345 gad_source=1" 
 =cut 
  
  
Design a Mobile Site
View Site in Mobile | Classic
Share by: