Manage offline conversions
Stay organized with collections
Save and categorize content based on your preferences.
Page Summary
Importing offline conversions using Google Ads API with enhanced conversions for leads improves accuracy by using hashed user data.
Implementation involves configuring tagging, normalizing/hashing user data, populating ClickConversion objects with identifiers and details, and importing them via the API.
Prerequisites include completing Google Ads API setup and enabling enhanced conversions for leads in Google Ads.
Review imports using the diagnostics report, noting that conversions report on the original click's impression date and may take time to appear.
Best practices include sending all available data, batching requests, using job_id for diagnostics, and avoiding custom variables with user data.
You can use the Google Ads API to import offline conversions into Google Ads to track ads
that led to sales in the offline world, such as over the phone or through a
sales rep.
In order to fully take advantage of the benefits of importing conversion data,
we recommend implementing enhanced conversions for leads, which leverages GCLIDs
and user-provided data to maximize durability and performance.
Enhanced conversions
Enhanced conversions help you to improve the accuracy of your conversion
measurement by supplementing your conversions with first-party conversion data,
like email address and phone number.
The following section explains how to enhance offline conversions, a feature
also referred to as enhanced conversions for leads.
What is enhanced conversions for leads?
Enhanced conversions for leads is an upgraded version of offline conversion
import that uses user-provided data, such as email addresses, to supplement
imported offline conversion data to improve accuracy and bidding performance.
When you import your offline conversions, the provided hashed customer data is
used to attribute back to the Google Ads campaign by matching to the same data
collected on your website, such as a, lead form, and to signed-in customers
who engaged with your ad. For more information, see
About enhanced conversions for leads.
There are two ways to implement enhanced conversions for leads depending on
whether you use the Google
tag to track form submission
events on your webpage. For the best performance and durability, we
recommend using Google tag for enhanced conversions for leads.
If you are starting from scratch, start with the
Prerequisites section.
If you already have offline conversion imports set up, and want to upgrade
to enhanced conversions for leads, we recommend starting with the Configure
tagging section.
If you've already set up the Google tag, or don't plan to use the Google
tag, and are starting work on your Google Ads API integration, jump to the API
Implementation
section.
You must opt in to enhanced conversions for leads and accept the customer data
terms before you can use enhanced conversions for leads. You can verify whether
these prerequisites are already met by issuing the following query to the Google Ads
conversion customer:
If either of accepted_customer_data_terms or
enhanced_conversions_for_leads_enabled is false, follow the instructions in
Create a new conversion action to complete these
prerequisites.
Configure tagging
Configure the Google tag to enable enhanced conversions for leads by following
the instructions to
Configure Google tag settings.
To set up enhanced conversions for leads with Google Tag Manager, follow these
instructions.
API Implementation
Here is the overall flow for importing enhanced conversions for leads using the
Google Ads API.
To upload conversions, you must specify the resource name of the
ConversionAction you've set up in Google Ads for
importing click conversions. You can find the resource name by querying the
conversion_action resource using the following
GAQL query:
For privacy considerations, the following data must be hashed using SHA-256
before being imported:
Email address
Phone number
To standardize the hash results, do the following prior to hashing these values:
Remove leading and trailing whitespaces.
Convert the text to lowercase.
Format phone numbers according to the E164
standard.
For email addresses:
Remove all periods (.) in the username (before the @ symbol). For
example, jane.doe@example.com becomes janedoe@example.com.
Remove the plus (+) symbol and any characters following it in the
username (before the @ symbol). For example,
janedoe+newsletter@example.com becomes janedoe@example.com.
Code example
This example shows how to normalize and hash user-provided data.
defnormalize_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 segmentsiflen(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.ifre.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)returnnormalize_and_hash(normalized_email)defnormalize_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. """returnhashlib.sha256(s.strip().lower().encode()).hexdigest()
# 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.defnormalize_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'.defnormalize_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.ifemail_parts.last=~/^(gmail|googlemail)\.com\s*/email_parts[0]=email_parts[0].gsub('.','')endnormalize_and_hash(email_parts.join('@'))end
subnormalize_and_hash{my$value=shift;# Removes leading, trailing, and intermediate spaces.$value=~s/\s+//g;returnsha256_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'.subnormalize_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];}returnnormalize_and_hash($normalized_email);}
The collection of ClickConversion objects in
your UploadClickConversionRequest represents the set of conversions you want
to import. Keep the following details in mind when creating ClickConversion
objects:
gclid
GCLIDs are identifiers
that are captured from URL parameters when an individual clicks on your ad and
navigates to your website.
gbraid
A gbraid is a URL
parameter that is present when a user clicks on an ad on the web and is
directed to your iOS app.
wbraid
A wbraid is a URL
parameter that is present when a user clicks on an ad in an iOS app and is
directed to your webpage.
cart_data
Contains item-level details about a conversion. Populating the cart_data
field is required to report on basket data—such as items purchased, price, and
quantity—in the Google Ads UI. See the CartData object
definition for the required structure, and learn more About conversions with
cart data.
user_identifiers
When using enhanced conversions for leads, you must populate the
user_identifiers field
with normalized and hashed user-provided data. If you have multiple user
identifiers available, create a separate UserIdentifier for each one, up to
five identifiers.
conversion_date_time
The date and time of the conversion.
The value must have a timezone specified, and the format must be
yyyy-mm-dd HH:mm:ss+|-HH:mm, for example: 2022-01-01 19:32:45-05:00 (ignoring daylight saving time)
.
The timezone can be for any valid value: it does not have to match the
account's timezone. However, if you plan on comparing your imported conversion
data with those in the Google Ads UI, we recommend using the same timezone as your
Google Ads account so that the conversion counts match. You can find more details
and examples in the Help
Center and check
the Codes and formats for a
list of valid timezone IDs.
conversion_action
The resource name of the ConversionAction
for the offline conversion.
The conversion action must have a type of UPLOAD_CLICKS, and must exist in
the Google Ads conversion customer of the Google Ads account associated with the click.
It's highly recommended that you populate the
consent field of the
ClickConversion object. If not set, it's possible that your conversions won't
be attributable.
order_id
Also known as the transaction ID for the conversion. This field is optional,
but strongly recommended, because it makes it simpler to reference imported
conversions when making adjustments.
If you set it during the import, you must use it for any adjustments. To learn
more about how to use a transaction ID to minimize duplicate conversions, see
this Help Center article.
Google Ads does not support custom conversion variables in combination with
wbraid or gbraid.
conversion_environment
Indicates the environment where this conversion was recorded. For example,
APP or WEB.
session_attributes_encoded and session_attributes_key_value_pairs
Session attributes represent aggregated identifiers used for conversion
attribution. These work in addition to GCLIDs and URL parameters (such as
GBRAIDs), and user-provided data, which is central to enhanced conversions for
leads. There are two ways to import session attributes: by providing the
encoded token generated by our Javascript code in the browser, or by providing
individual key value pairs for each of the identifiers.
To maximize your campaign performance, we recommend importing click
identifiers, user-provided data, and session attributes with all of your
conversions, if possible.
The IP address of the customer when they arrived on the landing page after an
ad click and before a conversion event. This is the IP address of the
customer's device, not the advertiser's server.
This field is a string representing an IP address in either
IPv4 or IPv6
format. For example:
IPv4: "192.0.2.0"
IPv6: "2001:0DB8:1234:5678:9999:1111:0000:0001"
Code example
This example shows how to set your normalized and hashed user-provided data onto
a ClickConversion object.
# 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.ifraw_record.get("phone")isnotNone: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)
# 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_conversiondo|cc|cc.conversion_action=client.path.conversion_action(customer_id,conversion_action_id)cc.conversion_date_time=conversion_date_timecc.conversion_value=conversion_value.to_fcc.currency_code='USD'unlessorder_id.nil?cc.order_id=order_idendunlessraw_record["gclid"].nil?cc.gclid=gclidend# Specifies whether user consent was obtained for the data you are# uploading. For more details, see:# https://www.google.com/about/company/user-consent-policyunlessraw_record["ad_user_data_consent"].nil?cc.consent=client.resource.consentdo|c|c.ad_user_data=ad_user_data_consentendend# Set one of the session_attributes_encoded or# session_attributes_key_value_pairs fields if either are provided.ifsession_attributes_encoded!=nilcc.class.module_eval{attr_accessor:session_attributes_encoded}cc.session_attributes_encoded=session_attributes_encodedelsifsession_attributes_hash!=nil# Add new attribute to click conversion objectcc.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_pairssession_attributes_hash.eachdo|key,value|pair=::Google::Ads::GoogleAds::V19::Services::SessionAttributeKeyValuePair.newpair.session_attribute_key=keypair.session_attribute_value=valuecc.session_attributes_key_value_pairs.key_value_pairs << pairendend# 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_identifierdo|ui|ui.hashed_email=normalize_and_hash_email(raw_record["email"])# Optional: Specifies the user identifier source.ui.user_identifier_source=:FIRST_PARTYend# Checks if the record has a phone number, and if so, adds a UserIdentifier# for it.unlessraw_record["phone"].nil?cc.user_identifiers << client.resource.user_identifierdo|ui|ui.hashed_phone_number=normalize_and_hash(raw_record["phone"])endendend
# Create an empty click conversion.my$click_conversion=Google::Ads::GoogleAds::V22::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::V22::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::V22::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::V22::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.# 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.ifraw_record.get("order_id"):click_conversion.order_id=raw_record["order_id"]# Sets the gclid if provided.ifraw_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-policyifraw_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.ifsession_attributes_encoded:click_conversion.session_attributes_encoded=session_attributes_encodedelifsession_attributes_dict:forkey,valueinsession_attributes_dict.items():pair:SessionAttributeKeyValuePair=client.get_type("SessionAttributeKeyValuePair")pair.session_attribute_key=keypair.session_attribute_value=valueclick_conversion.session_attributes_key_value_pairs.key_value_pairs.append(pair)
cc.conversion_action=client.path.conversion_action(customer_id,conversion_action_id)cc.conversion_date_time=conversion_date_timecc.conversion_value=conversion_value.to_fcc.currency_code='USD'unlessorder_id.nil?cc.order_id=order_idendunlessraw_record["gclid"].nil?cc.gclid=gclidend# Specifies whether user consent was obtained for the data you are# uploading. For more details, see:# https://www.google.com/about/company/user-consent-policyunlessraw_record["ad_user_data_consent"].nil?cc.consent=client.resource.consentdo|c|c.ad_user_data=ad_user_data_consentendend# Set one of the session_attributes_encoded or# session_attributes_key_value_pairs fields if either are provided.ifsession_attributes_encoded!=nilcc.class.module_eval{attr_accessor:session_attributes_encoded}cc.session_attributes_encoded=session_attributes_encodedelsifsession_attributes_hash!=nil# Add new attribute to click conversion objectcc.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_pairssession_attributes_hash.eachdo|key,value|pair=::Google::Ads::GoogleAds::V19::Services::SessionAttributeKeyValuePair.newpair.session_attribute_key=keypair.session_attribute_value=valuecc.session_attributes_key_value_pairs.key_value_pairs << pairendend
# Add details of the conversion.$click_conversion->{conversionAction}=Google::Ads::GoogleAds::V22::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::V22::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::V22::Services::ConversionUploadService::SessionAttributeKeyValuePair->new({sessionAttributeKey=>$key,sessionAttributeValue=>$value});push@{$click_conversion->{sessionAttributesKeyValuePairs}{keyValuePairs}},$pair;}}
Once your ClickConversion objects are configured and added to the
conversions field of the UploadClickConversionRequest object, set the
following fields and pass the request to the UploadClickConversions method on
the ConversionUploadService.
customer_id
Set this to the Google Ads conversion customer of the account that is the
source of the clicks. If you're unsure which account is the correct one, refer
to the customer.conversion_tracking_setting.google_ads_conversion_customer
field in the example query in the getting started
section.
job_id
Provides a mechanism for associating your import requests with the per-job
information in offline data
diagnostics.
If you don't set this field, the Google Ads API assigns each request a unique value
in the range of [2^31, 2^63). If you would prefer to group multiple requests
into a single logical job, set this field to the same value in the range [0,
2^31) on every request in your job.
The job_id in the response
contains the job ID for the request, regardless of whether you specified a
value or let the Google Ads API assign a value.
partial_failure
This field must be set to true when importing conversions. Follow the
partial failures guidelines when
processing the response.
Import the request
Once you've populated your ClickConversion objects, and constructed your
request, you can submit your import.
# 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_serviceresponse:UploadClickConversionsResponse=(conversion_upload_service.upload_click_conversions(customer_id=customer_id,conversions=[click_conversion],# Enables partial failure (must be true).partial_failure=True,))
response=client.service.conversion_upload.upload_click_conversions(customer_id:customer_id,conversions:[click_conversion],# Partial failure must be true.partial_failure:true,)ifresponse.partial_failure_errorputs"Partial failure encountered: #{response.partial_failure_error.message}"elseresult=response.results.firstputs"Uploaded click conversion that happened at #{result.conversion_date_time} "\"to #{result.conversion_action}."end
# 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_servicemy$response=$api_client->ConversionUploadService()->upload_click_conversions({customerId=>$customer_id,conversions=>[$click_conversion],# Enable partial failure (must be true).partialFailure=>"true"});
It takes up to three hours for imported conversion statistics to appear in your
Google Ads account for last-click attribution. For other search attribution models,
it can take longer than three hours. Consult the
data freshness guide for more
information.
When reporting on conversion metrics for your campaigns, refer to
Mapping user interface metrics to correlate
Google Ads UI metrics with Google Ads API reporting fields. You can also query the
conversion_action resource to view the total
number of conversions and the total conversion value for a given conversion
action.
Best practices
Keep the following best practices in mind when implementing enhanced conversions
for leads.
Send all conversion data regardless of completeness
To ensure full and accurate conversion reporting, import all available offline
conversion events, including those that might not have come from Google Ads.
Conversions that include only user-provided data are still useful and can
contribute positively to Google Ads campaign optimization.
If you assign an order_id to a conversion, we recommend including it. If you
have the GCLID for a conversion, we recommend sending it in addition to the
user_identifiers for improved performance. Furthermore, if you have more than
one UserIdentifier for the conversion, include all of them on the
ClickConversion object to improve the likelihood of a match.
Batch multiple conversions in a single request
If you have multiple conversions to import, batch the conversions into one
UploadClickConversionsRequest,
rather than sending an import request per conversion.
Check the quota guide
for limits on the number of conversions per request.
If you want offline data diagnostics to
group a set of requests under the same logical job, set the job_id of all the requests to the
same value. This can be useful if you have a single job or process that imports
a large number of conversions using multiple requests. If you set the job_id
on each of those requests to the same value, then you can retrieve a single
entry for the job from
job_summaries.
If instead you let the Google Ads API assign a system-generated value to the job_id
of each request, the job_summaries contains a separate entry for each request,
which could make analyzing the overall health of your job more challenging.
Don't use external attribution data
When using enhanced conversions for leads, don't set external_attribution_data
on the ClickConversion or specify a conversion_action that uses an external
attribution model. Google Ads doesn't support externally attributed conversions for
imports using user-provided data.
Don't include custom variables
When using enhanced conversions for leads, don't include any
custom_variables. Google Ads
doesn't support the use of custom variables with user-provided data in
conversion imports. If custom variables are included with conversions that
contain user-provided data, those conversions will be considered invalid and
dropped.
Troubleshooting
Offline data diagnostics provide a
single resource for reviewing the overall health of your imports on an ongoing
basis. However, during implementation you can use the information in this
section to investigate any errors reported in the partial_failure_error field of the
response.
Some of the most common errors when importing conversion actions are
authorization errors, such as USER_PERMISSION_DENIED. Double check that you
have the customer ID in your request set to the Google Ads conversion customer which
owns the conversion action. Visit our authorization
guide for more details and see our common errors
guide for tips on how to debug these
different errors.
The specified conversion action is either not enabled, or cannot be accessed by the client
account specified by the `client_id` field in the request. Make sure the conversion action
in your upload is enabled and is owned by the customer sending the upload request.
This error may also occur if the GCLID in the request belongs to a client account that does
not have access to the conversion action specified in the request. You can verify whether a
GCLID belongs to a client account using the
click_view resource, by submitting a query
that filters by click_view.gclid and
segments.date, where the date is the date the click occurred.
The specified conversion action has a type that is not valid for enhanced
conversions for leads. Make sure the ConversionAction specified in
your upload request has type UPLOAD_CLICKS.
No click was found that matched the provided user identifiers. The Google Ads API
only returns this error if debug_enabled is true on
the UploadClickConversionsRequest.
If a conversion encounters this warning, the Google Ads API
includes it in the successful_event_count of your offline data diagnostics. The
Google Ads API includes an entry for CLICK_NOT_FOUND in the
alerts
collection so you can monitor the frequency of this warning.
This error is expected if the click is not from a Google Ads campaign. For example it may come
from SA360 or
DV360. Other
possible causes are as follows:
In rare instances where the uploading customer is different from the Google Ads
conversion customer, this error can mean that the uploading customer has
accepted the customer data terms, but the serving customer has not.
You can determine if an
account has accepted the customer data terms by querying the
customer resource and checking the
customer.offline_conversion_tracking_info.accepted_customer_data_terms field.
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2025-12-19 UTC."],[],[]]