Send events

  • Use the Data Manager API to send online or offline event data for conversion tracking and improved ad performance.

  • To send event data, you need to prepare a Destination and format, hash, and encode your event data into an Event object.

  • Combine the Destination and Events into a request body, updating placeholders with your account and destination details.

  • Send the request using the Google APIs Explorer and review the response, which will include a requestId for successful requests or error details for failures.

  • You can send events for multiple destinations in a single request by using destination references.

Work through this guide to get familiar with the process of sending events with the Data Manager API.

Use the Data Manager API for either of the following scenarios:

  • Send Google Ads tag conversions or Google Analytics purchase events as an additional data source for your tag conversions, to maximize ad interaction signals and strengthen your data and overall performance.

    This feature is available to all Google Ads accounts, but is only available to Google Analytics properties on an allowlist. Fill out the form if you're interested in adding your Google Analytics property to the allowlist.

  • Send event data for Google Ads offline conversions or enhanced conversions for leads .

In this guide, you complete the following steps:

  1. Prepare a Destination to receive event data.
  2. Prepare event data to send.
  3. Build an IngestionService request for events.
  4. Send the request with the Google APIs Explorer.
  5. Understand success and failure responses.

Prepare destinations

Before you can send data, you need to prepare at least one Destination for the data. Here are the fields of a Destination . Check out Configure destinations for more details and examples of destinations for different scenarios.

Select the tab that corresponds to your use case.

  • Select Advertiser if you're using credentials for a Google Account that is a user in the advertiser accounts you want to manage.
  • Select Data Partner if you're using credentials for a Google Account that is a user in a data partner account, and you want to manage advertiser accounts that have a partner link to the data partner account.

Advertiser

operatingAccount

The account that receives the events.

For events sent as an additional data source, the operating account can be either a Google Ads account or a Google Analytics property.

If the accountType is GOOGLE_ANALYTICS_PROPERTY , the request's credentials must be for a Google Analytics user with the Editor or Administrator role for the property.

For offline conversions, enhanced conversions for leads, and conversions with cart data, the operating account must be a Google Ads account.

loginAccount

The account where the Google Account for the credentials is a user.

productDestinationId

The ID of the entity in the operatingAccount that receives the events.

For events sent as an additional data source, the productDestinationId must be one of the following:

  1. The ID of a Google Ads conversion with type set to WEBPAGE . In the Google Ads UI, the Conversion sourcefor a WEBPAGE conversion action is Website.

  2. The measurement ID of a Google Analytics Web stream. You can't send events as an additional data source to a Google Analytics iOS app or Android app stream.

For offline conversions or enhanced conversions for leads, the productDestinationId must be the ID of a Google Ads conversion action with type set to UPLOAD_CLICKS . In the Google Ads UI, the Conversion sourcefor an UPLOAD_CLICKS conversion action is Website (Import from clicks).

  { 
  
 "operatingAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " OPERATING_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " OPERATING_ACCOUNT_ID 
" 
  
 }, 
  
 "loginAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " LOGIN_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " LOGIN_ACCOUNT_ID 
" 
  
 }, 
  
 "productDestinationId" 
 : 
  
 " PRODUCT_DESTINATION_ID 
" 
 } 
 

Data Partner

operatingAccount
The account that receives the events. Must be a Google Ads account. The Data Manager API doesn't support sending events to a Google Analytics property using a linked data partner.
loginAccount

The account where the credential's user has access.

Set the accountId to your Data Partner account ID, and set the accountType to DATA_PARTNER .

linkedAccount

The account with an established product link through which the credential's user has access to the operatingAccount .

If a parent of the operatingAccount is linked to your data partner account, set the linkedAccount to the parent of the operatingAccount . If the operatingAccount is directly linked to your data partner account, don't set linkedAccount .

productDestinationId

The ID of the entity in the operatingAccount that receives the events.

For events sent as an additional data source using a linked data partner, the productDestinationId must be the ID of a Google Ads conversion with type set to WEBPAGE . In the Google Ads UI, the Conversion sourcefor a WEBPAGE conversion action is Website.

For offline conversions or enhanced conversions for leads, the productDestinationId must be the ID of a Google Ads conversion action with type set to UPLOAD_CLICKS . In the Google Ads UI, the Conversion sourcefor an UPLOAD_CLICKS conversion action is Website (Import from clicks).

  { 
  
 "operatingAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " OPERATING_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " OPERATING_ACCOUNT_ID 
" 
  
 }, 
  
 "loginAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " DATA_PARTNER 
" 
 , 
  
 "accountId" 
 : 
  
 " DATA_PARTNER_ACCOUNT_ID 
" 
  
 }, 
  
 "linkedAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " LINKED_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " LINKED_ACCOUNT_ID 
" 
  
 }, 
  
 "productDestinationId" 
 : 
  
 " PRODUCT_DESTINATION_ID 
" 
 } 
 

The example in this guide shows how to construct a request that sends every event to the same destination. If you want to send events for multiple destinations in the same request, see send events for multiple destinations .

Prepare event data

Consider the following event data. Each table corresponds to one conversion event. Each conversion event has a timestamp of the event, its conversion action, and conversion value.

Each event might have ad identifiers, like gclid , or user identifiers, like email addresses, phone numbers, and address information . An event can also have:

Here's the event data:

Event 1

Event #1
conversion_time
2025-06-10 15:07:01-05:00
conversion_action_id
123456789
transaction_id
ABC798654321
conversion_value
30.03
currency
USD
gclid
GCLID_1
emails
given_name
John
family_name
Smith-Jones
region_code
us
postal_code
94045
customer_type
NEW
customer_value_bucket
HIGH
client_id
1234567890.1761581763
user_id
user_ABC12345
ad_unit_name
Banner_01
event_name
purchase
Cart items
item_id
SKU_12345
item_name
Stan and Friends Tee
item_affiliation
Google Merchandise Store
item_coupon
SUMMER_FUN
item_discount
2.22
item_index
0
item_brand
Google
item_category
Apparel
item_category2
Adult
item_category3
Shirts
item_category4
Crew
item_category5
Short sleeve
item_list_id
related_products
item_list_name
Related Products
item_price
10.01
item_quantity
3

Event 2

Event #2
conversion_time
June 10, 2025 11:42:33PM America/New_York
conversion_action_id
123456789
transaction_id
DEF999911111
conversion_value
42.02
currency
eur
gclid
GCLID_2
emails

zoe@EXAMPLE.COM

cloudy.sanfrancisco@gmail.com

given_name
zoë
family_name
pérez
region_code
PT
postal_code
1229-076
customer_type
RETURNING
client_id
9876543210.1761582117
user_id
user_DEF9876
ad_unit_name
Banner_02
event_name
purchase
Cart items
item_id
SKU_12346
item_name
Google Grey Women's Tee
item_affiliation
Google Merchandise Store
item_coupon
SUMMER_FUN
item_discount
3.33
item_index
1
item_brand
Google
item_category
Apparel
item_category2
Adult
item_category3
Shirts
item_category4
Crew
item_category5
Short sleeve
item_list_id
related_products
item_list_name
Related Products
item_price
21.01
item_quantity
2

Format the data

Format the fields according as specified in the formatting guide . Here's the event data after formatting:

Event 1

Event #1
conversion_time
2025-06-10T15:07:01-05:00
conversion_action_id
123456789
transaction_id
ABC798654321
conversion_value
30.03
currency
USD
gclid
GCLID_1
emails
given_name
john
family_name
smith-jones
region_code
US
postal_code
94045
customer_type
NEW
customer_value_bucket
HIGH
client_id
1234567890.1761581763
user_id
user_ABC12345
ad_unit_name
Banner_01
event_name
purchase
Cart items
item_id
SKU_12345
item_name
Stan and Friends Tee
item_affiliation
Google Merchandise Store
item_coupon
SUMMER_FUN
item_discount
2.22
item_index
0
item_brand
Google
item_category
Apparel
item_category2
Adult
item_category3
Shirts
item_category4
Crew
item_category5
Short sleeve
item_list_id
related_products
item_list_name
Related Products
item_price
10.01
item_quantity
3

Event 2

Event #2
conversion_time
2025-06-10T23:42:33-05:00
conversion_action_id
123456789
transaction_id
DEF999911111
conversion_value
42.02
currency
EUR
gclid
GCLID_2
emails

zoe@example.com

cloudysanfrancisco@gmail.com

given_name
zoë
family_name
pérez
region_code
PT
postal_code
1229-076
customer_type
RETURNING
client_id
9876543210.1761582117
user_id
user_DEF9876
ad_unit_name
Banner_02
event_name
purchase
Cart items
item_id
SKU_12346
item_name
Google Grey Women's Tee
item_affiliation
Google Merchandise Store
item_coupon
SUMMER_FUN
item_discount
3.33
item_index
1
item_brand
Google
item_category
Apparel
item_category2
Adult
item_category3
Shirts
item_category4
Crew
item_category5
Short sleeve
item_list_id
related_products
item_list_name
Related Products
item_price
21.01
item_quantity
2

Hash and encode the data

In addition, the formatted email addresses, given names, and family names must be hashed using the SHA-256 algorithm and encoded using either hex or Base64 encoding. Here's the event data after formatting, hashing, and encoding using hex encoding:

Event 1

Event #1
conversion_time
2025-06-10T15:07:01-05:00
conversion_action_id
123456789
transaction_id
ABC798654321
conversion_value
30.03
currency
USD
gclid
GCLID_1
emails
given_name
96D9632F363564CC3032521409CF22A852F2032EEC099ED5967C0D000CEC607A
family_name
DB98D2607EFFFA28AFF66975868BF54C075ECA7157E35064DCE08E20B85B1081
region_code
US
postal_code
94045
customer_type
NEW
customer_value_bucket
HIGH
client_id
1234567890.1761581763
user_id
user_ABC12345
ad_unit_name
Banner_01
event_name
purchase
Cart items
item_id
SKU_12345
item_name
Stan and Friends Tee
item_affiliation
Google Merchandise Store
item_coupon
SUMMER_FUN
item_discount
2.22
item_index
0
item_brand
Google
item_category
Apparel
item_category2
Adult
item_category3
Shirts
item_category4
Crew
item_category5
Short sleeve
item_list_id
related_products
item_list_name
Related Products
item_price
10.01
item_quantity
3

Event 2

Event #2
conversion_time
2025-06-10T23:42:33-05:00
conversion_action_id
123456789
transaction_id
DEF999911111
conversion_value
42.02
currency
EUR
gclid
GCLID_2
emails

3E693CF7E5B67880BFF33B2D2626DADB7BF1D4BC737192E47CF8BAA89ACF2250

223EBDA6F6889B1494551BA902D9D381DAF2F642BAE055888E96343D53E9F9C4

given_name
2752B88686847FA5C86F47B94CE652B7B3F22A91C37617D451A4DB9AFA431450
family_name
6654977D57DDDD3C0329CA741B109EF6CD6430BEDD00008AAD213DF25683D77F
region_code
PT
postal_code
1229-076
customer_type
RETURNING
client_id
9876543210.1761582117
user_id
user_DEF9876
ad_unit_name
Banner_02
event_name
purchase
Cart items
item_id
SKU_12346
item_name
Google Grey Women's Tee
item_affiliation
Google Merchandise Store
item_coupon
SUMMER_FUN
item_discount
3.33
item_index
1
item_brand
Google
item_category
Apparel
item_category2
Adult
item_category3
Shirts
item_category4
Crew
item_category5
Short sleeve
item_list_id
related_products
item_list_name
Related Products
item_price
21.01
item_quantity
2

Convert the data to Event objects

Convert each event's formatted and hashed data to an Event . Populate the following fields as indicated:

  1. Set eventTimestamp to the time the event occurred.

    Events for Google Analytics must have an eventTimestamp within the last 72 hours.

  2. Set required fields for your use case.

    Use case
    Identifiers
    transactionId
    eventSource
    Offline conversions or enhanced conversions for leads
    Required . Set at least one of the following:
    Optional
    Required . Set to one of the enum values for EventSource .
    Events sent as an additional data source to a Google Ads destination
    Required . Set at least one of the following:
    Required
    Optional. If set, must be WEB .
    Events sent as an additional data source to a Google Analytics destination
    Required . Set at least one of the following:
    Required
    Optional. If set, must be WEB .
  3. If you're sending events as an additional data source, review How Google handles additional data source data .

  4. If you're sending Google Ads conversions with cart data or Google Analytics purchase events, add cart data to the events.

  5. Populate any other fields where you have a value for the event. Refer to the Event reference documentation for the complete list of available fields.

How Google handles additional data source data

Within the same conversion action, Google uses the transactionId to deduplicate conversion events sent from different sources (like your website tag and Data Manager API ingestion requests). The following table explains how the data from your ingestion requests is processed.

Scenario Data field How it's handled
transactionId MATCHES an existing tag event
conversionValue (with currencyCode )

Updated. The conversionValue (with currencyCode ) from the Event overrides the original value recorded by the tag.

Note: During the initial 14-day trial period for a conversion action, value updates are disabled. The tag's value won't be overridden in Google Ads reporting until the trial period ends.

transactionId MATCHES an existing tag event
Other fields except conversionValue or currencyCode (for example, adIdentifiers.gclid ) Ignored. Other field values from your additional data source won't overwrite the field values originally recorded by your Google tag for matched transactions.
transactionId does NOT match any existing event
All provided data (for example, userData , conversionValue , currencyCode )

Used to create a new conversion event. Google will then try to attribute this new conversion to an ad click using the identifiers you provided (like adIdentifiers.gclid or userData ).

Note: During the initial 14-day trial period, these newly created conversions will appear in your reporting but won't be used for bidding. After the trial ends, they will automatically become biddable.

Add session attributes

If you're sending offline conversions or enhanced conversions for leads, add session attributes when other ad identifiers such as a GCLID or WBRAID aren't available. You can also include session attributes in addition to other ad identifiers.

Session attributes provide further context and signals about the user's interaction with your website, which can enhance conversion measurement, reporting and bidding accuracy.

In the Data Manager API, there are two approaches you can use to send session attributes:

  1. Recommended:Set the sessionAttributes field of adIdentifiers to the base64 encoded session attributes string. Follow the instructions in How to capture session_attributes to modify your form submission pages to capture the encoded string.

  2. If you can't use JavaScript, capture the individual session attribute fields and add each to the experimentalFields list as a separate ExperimentalField :

    • gad_campaignid
    • session_start_time_usec
    • gad_source
    • landing_page_url
    • landing_page_referrer

    If you have a value for the landing_page_user_agent session attribute, send it in the userAgent field of adIdentifiers.landingPageDeviceInfo .

    Here are the best practices when sending individual key-value pairs:

    • Consistently send gad_campaignid and session_start_time_usec . These fields are crucial for accurate attribution.
    • Don't provide an inaccurate or partial landing_page_url value such as a placeholder string, an internal application path, or an incomplete URL. Omit the landing_page_url if you don't have the accurate, full URL.

    Here's a portion of a sample event with entries in experimentalFields for gad_campaignid and session_start_time_usec , and the user agent in the landingPageDeviceInfo field:

      { 
      
     ... 
     , 
      
     "experimentalFields" 
     : 
      
     [ 
      
     { 
      
     "field" 
     : 
      
     "gad_campaignid" 
     , 
      
     "value" 
     : 
      
     "21288051566" 
      
     }, 
      
     { 
      
     "field" 
     : 
      
     "session_start_time_usec" 
     , 
      
     "value" 
     : 
      
     "1767711548052000" 
      
     } 
      
     ], 
      
     "adIdentifiers" 
     : 
      
     { 
      
     "landingPageDeviceInfo" 
     : 
      
     { 
      
     "userAgent" 
     : 
      
     "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36" 
      
     } 
      
     } 
     } 
     
    

Add cart data

Populate the cartData field of the Event with information about the items associated with the event. Use this field when sending Google Analytics purchase events or Google Ads conversions with cart data parameters .

Populate the following fields for CartData as needed.

items
Required. Include at least one item in this list.

The merchantId , merchantFeedLabel , and merchantFeedLanguageCode are all optional. Set these fields for Google Ads conversions with cart data if the items in the event include products that exist in several Merchant Center accounts.

merchantId
Optional. The Merchant Center account ID.
merchantFeedLabel
Optional. The feed label of the Merchant Center feed. Feed labels allow you to categorize products for campaign targeting. For example, you can use feed labels to organize products by language. If your campaign targets products based on country, use the 2-letter country code in ISO-3166-1 alpha-2 format. Example: US .
merchantFeedLanguageCode
Optional. The ISO 639-1 language code associated with the Merchant Center feed where your items are uploaded. Example: en .

Add item-level cart data

Add one or more Item objects to the items list of CartData . The items list must not be empty for Google Ads conversions with cart data and Google Analytics purchase events.

Populate the following fields for each Item as needed.

items.itemId
Requiredfor Google Analytics purchase events. A unique identifier for the item.
items.merchantProductId
Requiredfor Google Ads conversions with cart data. The product ID within the Merchant Center account.
items.unitPrice

Required. The unit price excluding tax, shipping, and event scoped (transaction-level) discounts for this item.

If the item has an item scoped discount, use the discounted unit price. For example, if an item has a unit price of 27.67 and a unit discount of 6.66 , then set unitPrice to 21.01 .

items.quantity

Required. The quantity of units purchased for this particular item.

items.additionalItemParameters

Optional. Populate this list with any item scoped parameters that aren't captured in the other Item fields. These are primarily used by Google Analytics purchase events. Use the parameter name from the Google Analytics documentation as the parameterName of the ItemParameter .

For example, for Google Analytics, if you have the brand and category for an item, add an entry to the item's additionalItemParameters with parameterName set to item_brand and value set to the brand name.

We don't recommend adding entries to additionalItemParameters for the quantity , price , or item_id item parameters. Instead, populate the itemId , unitPrice , and quantity fields of the Item , which take precedence over any entries in additionalItemParameters .

Add Google Analytics information

If the destinations for an event sent as an additional data source include a Google Analytics property, populate the following fields as indicated:

eventName

Required. The name of the Google Analytics event.

transactionId

Required. The unique identifier for the event.

At least one identifier

At least one of the following fields must be set:

destinationReferences

Requiredif the request-level destinations list contains more than one Google Analytics Destination . Add an entry to destinationReferences to specify which Google Analytics destination should receive the event. See send events to multiple destinations for more information about destination references.

If destinationReferences isn't set or has multiple entries that refer to Google Analytics destinations, the Data Manager API rejects the event with the error MULTIPLE_DESTINATIONS_FOR_GOOGLE_ANALYTICS_EVENT .

cartData

Required. Learn how to add cart data to your purchase events.

userId

Optional. The User-ID for the user.

additionalEventParameters

Optional, but recommended. Populate this list with any Google Analytics event parameters that aren't captured in the other Event fields. The parameters can include additional recommended parameters from the purchase event or any other parameters you want to capture. Use the Google Analytics parameter name for the parameterName of the EventParameter .

For example, if you have the taxes associated with a transaction, add an entry to additionalEventParameters with parameterName set to tax , and value set to the tax cost.

We don't recommend adding entries to additionalItemParameters for the transactionId , currency , or value Google Analytics event parameters. Instead, populate the transactionId , currency , and conversionValue fields of the Event , which take precedence over any entries in additionalEventParameters .

Here's a sample Event for the formatted, hashed, and encoded data from the second event, with additional data for Google Analytics:

  { 
  
 "adIdentifiers" 
 : 
  
 { 
  
 "gclid" 
 : 
  
 "GCLID_2" 
  
 }, 
  
 "conversionValue" 
 : 
  
 42.02 
 , 
  
 "currency" 
 : 
  
 "EUR" 
 , 
  
 "eventTimestamp" 
 : 
  
 "2025-06-10T23:42:33-05:00" 
 , 
  
 "transactionId" 
 : 
  
 "DEF999911111" 
 , 
  
 "eventSource" 
 : 
  
 "WEB" 
 , 
  
 "userData" 
 : 
  
 { 
  
 "userIdentifiers" 
 : 
  
 [ 
  
 { 
  
 "emailAddress" 
 : 
  
 "3E693CF7E5B67880BFF33B2D2626DADB7BF1D4BC737192E47CF8BAA89ACF2250" 
  
 }, 
  
 { 
  
 "emailAddress" 
 : 
  
 "223EBDA6F6889B1494551BA902D9D381DAF2F642BAE055888E96343D53E9F9C4" 
  
 }, 
  
 { 
  
 "address" 
 : 
  
 { 
  
 "givenName" 
 : 
  
 "2752B88686847FA5C86F47B94CE652B7B3F22A91C37617D451A4DB9AFA431450" 
 , 
  
 "familyName" 
 : 
  
 "6654977D57DDDD3C0329CA741B109EF6CD6430BEDD00008AAD213DF25683D77F" 
 , 
  
 "regionCode" 
 : 
  
 "PT" 
 , 
  
 "postalCode" 
 : 
  
 "1229-076" 
  
 } 
  
 } 
  
 ], 
  
 }, 
  
 "userProperties" 
 : 
  
 { 
  
 "customerType" 
 : 
  
 "RETURNING" 
  
 }, 
  
 "eventName" 
 : 
  
 "purchase" 
 , 
  
 "clientId" 
 : 
  
 "9876543210.1761582117" 
 , 
  
 "userId" 
 : 
  
 "user_DEF9876" 
 , 
  
 "additionalEventParameters" 
 : 
  
 [ 
  
 { 
  
 "parameterName" 
 : 
  
 "ad_unit_name" 
 , 
  
 "value" 
 : 
  
 "Banner_02" 
  
 } 
  
 ], 
  
 "cartData" 
 : 
  
 { 
  
 "transactionDiscount" 
 : 
  
 6.66 
 , 
  
 "items" 
 : 
  
 [ 
  
 { 
  
 "itemId" 
 : 
  
 "SKU_12346" 
 , 
  
 "quantity" 
 : 
  
 2 
 , 
  
 "unitPrice" 
 : 
  
 21.01 
 , 
  
 "additionalItemParameters" 
 : 
  
 [ 
  
 { 
  
 "parameterName" 
 : 
  
 "item_name" 
 , 
  
 "value" 
 : 
  
 "Google Grey Women's Tee" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "affiliation" 
 , 
  
 "value" 
 : 
  
 "Google Merchandise Store" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "coupon" 
 , 
  
 "value" 
 : 
  
 "SUMMER_FUN" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "discount" 
 , 
  
 "value" 
 : 
  
 "3.33" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "index" 
 , 
  
 "value" 
 : 
  
 "1" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_brand" 
 , 
  
 "value" 
 : 
  
 "Google" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category" 
 , 
  
 "value" 
 : 
  
 "Apparel" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category2" 
 , 
  
 "value" 
 : 
  
 "Adult" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category3" 
 , 
  
 "value" 
 : 
  
 "Shirts" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category4" 
 , 
  
 "value" 
 : 
  
 "Crew" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category5" 
 , 
  
 "value" 
 : 
  
 "Short sleeve" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_list_id" 
 , 
  
 "value" 
 : 
  
 "related_products" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_list_name" 
 , 
  
 "value" 
 : 
  
 "Related Products" 
  
 } 
  
 ] 
  
 } 
  
 ] 
  
 } 
 } 
 

Build the request body

To build the request body, combine the destinations and events , set the encoding field, and add any other request fields you want to include such as validateOnly and consent .

The examples in this guide don't use encryption, but you can follow the instructions in Encrypt user data to add encryption to your process.

Send the request

Here are the steps to try a request from your browser:

  1. Select the RESTtab and click Open in API Explorerto open the API Explorer in a new tab or window.
  2. In the request body in the API Explorer, replace each string beginning with REPLACE_WITH , such as REPLACE_WITH_OPERATING_ACCOUNT_TYPE , with the relevant value.
  3. Click Executeat the bottom of the API Explorer page and complete the authorization prompts to send the request.
  4. Set validateOnly to true to validate the request without applying the changes. When you're ready to apply the changes, set validateOnly to false .

If you installed a client library , select the tab for your chosen programming language to see a complete code sample of how to construct and send a request.

REST

Advertiser

 { 
  
 "destinations" 
 : 
  
 [ 
  
 { 
  
 "operatingAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " OPERATING_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " OPERATING_ACCOUNT_ID 
" 
  
 }, 
  
 "loginAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " LOGIN_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " LOGIN_ACCOUNT_ID 
" 
  
 }, 
  
 "productDestinationId" 
 : 
  
 " CONVERSION_ACTION_ID 
" 
  
 } 
  
 ], 
  
 "encoding" 
 : 
  
 "HEX" 
 , 
  
 "events" 
 : 
  
 [ 
  
 { 
  
 "adIdentifiers" 
 : 
  
 { 
  
 "gclid" 
 : 
  
 "GCLID_1" 
  
 }, 
  
 "conversionValue" 
 : 
  
 30.03 
 , 
  
 "currency" 
 : 
  
 "USD" 
 , 
  
 "eventTimestamp" 
 : 
  
 "2025-06-10T20:07:01Z" 
 , 
  
 "transactionId" 
 : 
  
 "ABC798654321" 
 , 
  
 "eventSource" 
 : 
  
 "WEB" 
 , 
  
 "userData" 
 : 
  
 { 
  
 "userIdentifiers" 
 : 
  
 [ 
  
 { 
  
 "address" 
 : 
  
 { 
  
 "givenName" 
 : 
  
 "96D9632F363564CC3032521409CF22A852F2032EEC099ED5967C0D000CEC607A" 
 , 
  
 "familyName" 
 : 
  
 "DB98D2607EFFFA28AFF66975868BF54C075ECA7157E35064DCE08E20B85B1081" 
 , 
  
 "regionCode" 
 : 
  
 "US" 
 , 
  
 "postalCode" 
 : 
  
 "94045" 
  
 } 
  
 } 
  
 ] 
  
 }, 
  
 "userProperties" 
 : 
  
 { 
  
 "customerType" 
 : 
  
 "NEW" 
 , 
  
 "customerValueBucket" 
 : 
  
 "HIGH" 
  
 }, 
  
 "eventName" 
 : 
  
 "purchase" 
 , 
  
 "clientId" 
 : 
  
 "1234567890.1761581763" 
 , 
  
 "userId" 
 : 
  
 "user_ABC12345" 
 , 
  
 "additionalEventParameters" 
 : 
  
 [ 
  
 { 
  
 "parameterName" 
 : 
  
 "ad_unit_name" 
 , 
  
 "value" 
 : 
  
 "Banner_01" 
  
 } 
  
 ], 
  
 "cartData" 
 : 
  
 { 
  
 "transactionDiscount" 
 : 
  
 6.66 
 , 
  
 "items" 
 : 
  
 [ 
  
 { 
  
 "itemId" 
 : 
  
 "SKU_12345" 
 , 
  
 "quantity" 
 : 
  
 3 
 , 
  
 "unitPrice" 
 : 
  
 10.01 
 , 
  
 "additionalItemParameters" 
 : 
  
 [ 
  
 { 
  
 "parameterName" 
 : 
  
 "item_name" 
 , 
  
 "value" 
 : 
  
 "Stan and Friends Tee" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "affiliation" 
 , 
  
 "value" 
 : 
  
 "Google Merchandise Store" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "coupon" 
 , 
  
 "value" 
 : 
  
 "SUMMER_FUN" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "discount" 
 , 
  
 "value" 
 : 
  
 "2.22" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "index" 
 , 
  
 "value" 
 : 
  
 "0" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_brand" 
 , 
  
 "value" 
 : 
  
 "Google" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category" 
 , 
  
 "value" 
 : 
  
 "Apparel" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category2" 
 , 
  
 "value" 
 : 
  
 "Adult" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category3" 
 , 
  
 "value" 
 : 
  
 "Shirts" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category4" 
 , 
  
 "value" 
 : 
  
 "Crew" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category5" 
 , 
  
 "value" 
 : 
  
 "Short sleeve" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_list_id" 
 , 
  
 "value" 
 : 
  
 "related_products" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_list_name" 
 , 
  
 "value" 
 : 
  
 "Related Products" 
  
 } 
  
 ] 
  
 } 
  
 ] 
  
 } 
  
 }, 
  
 { 
  
 "adIdentifiers" 
 : 
  
 { 
  
 "gclid" 
 : 
  
 "GCLID_2" 
  
 }, 
  
 "conversionValue" 
 : 
  
 42.02 
 , 
  
 "currency" 
 : 
  
 "EUR" 
 , 
  
 "eventTimestamp" 
 : 
  
 "2025-06-11T04:42:33Z" 
 , 
  
 "transactionId" 
 : 
  
 "DEF999911111" 
 , 
  
 "eventSource" 
 : 
  
 "WEB" 
 , 
  
 "userData" 
 : 
  
 { 
  
 "userIdentifiers" 
 : 
  
 [ 
  
 { 
  
 "emailAddress" 
 : 
  
 "3E693CF7E5B67880BFF33B2D2626DADB7BF1D4BC737192E47CF8BAA89ACF2250" 
  
 }, 
  
 { 
  
 "emailAddress" 
 : 
  
 "223EBDA6F6889B1494551BA902D9D381DAF2F642BAE055888E96343D53E9F9C4" 
  
 }, 
  
 { 
  
 "address" 
 : 
  
 { 
  
 "givenName" 
 : 
  
 "2752B88686847FA5C86F47B94CE652B7B3F22A91C37617D451A4DB9AFA431450" 
 , 
  
 "familyName" 
 : 
  
 "6654977D57DDDD3C0329CA741B109EF6CD6430BEDD00008AAD213DF25683D77F" 
 , 
  
 "regionCode" 
 : 
  
 "PT" 
 , 
  
 "postalCode" 
 : 
  
 "1229-076" 
  
 } 
  
 } 
  
 ] 
  
 }, 
  
 "userProperties" 
 : 
  
 { 
  
 "customerType" 
 : 
  
 "RETURNING" 
  
 }, 
  
 "eventName" 
 : 
  
 "purchase" 
 , 
  
 "clientId" 
 : 
  
 "9876543210.1761582117" 
 , 
  
 "userId" 
 : 
  
 "user_DEF9876" 
 , 
  
 "additionalEventParameters" 
 : 
  
 [ 
  
 { 
  
 "parameterName" 
 : 
  
 "ad_unit_name" 
 , 
  
 "value" 
 : 
  
 "Banner_02" 
  
 } 
  
 ], 
  
 "cartData" 
 : 
  
 { 
  
 "transactionDiscount" 
 : 
  
 6.66 
 , 
  
 "items" 
 : 
  
 [ 
  
 { 
  
 "itemId" 
 : 
  
 "SKU_12346" 
 , 
  
 "quantity" 
 : 
  
 2 
 , 
  
 "unitPrice" 
 : 
  
 21.01 
 , 
  
 "additionalItemParameters" 
 : 
  
 [ 
  
 { 
  
 "parameterName" 
 : 
  
 "item_name" 
 , 
  
 "value" 
 : 
  
 "Google Grey Women's Tee" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "affiliation" 
 , 
  
 "value" 
 : 
  
 "Google Merchandise Store" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "coupon" 
 , 
  
 "value" 
 : 
  
 "SUMMER_FUN" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "discount" 
 , 
  
 "value" 
 : 
  
 "3.33" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "index" 
 , 
  
 "value" 
 : 
  
 "1" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_brand" 
 , 
  
 "value" 
 : 
  
 "Google" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category" 
 , 
  
 "value" 
 : 
  
 "Apparel" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category2" 
 , 
  
 "value" 
 : 
  
 "Adult" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category3" 
 , 
  
 "value" 
 : 
  
 "Shirts" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category4" 
 , 
  
 "value" 
 : 
  
 "Crew" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category5" 
 , 
  
 "value" 
 : 
  
 "Short sleeve" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_list_id" 
 , 
  
 "value" 
 : 
  
 "related_products" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_list_name" 
 , 
  
 "value" 
 : 
  
 "Related Products" 
  
 } 
  
 ] 
  
 } 
  
 ] 
  
 } 
  
 } 
  
 ], 
  
 "validateOnly" 
 : 
  
 true 
 } 

Data Partner

 { 
  
 "destinations" 
 : 
  
 [ 
  
 { 
  
 "operatingAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " OPERATING_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " OPERATING_ACCOUNT_ID 
" 
  
 }, 
  
 "loginAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 "DATA_PARTNER" 
 , 
  
 "accountId" 
 : 
  
 " DATA_PARTNER_ACCOUNT_ID 
" 
  
 }, 
  
 "linkedAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " LINKED_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " LINKED_ACCOUNT_ID 
" 
  
 }, 
  
 "productDestinationId" 
 : 
  
 " CONVERSION_ACTION_ID 
" 
  
 } 
  
 ], 
  
 "encoding" 
 : 
  
 "HEX" 
 , 
  
 "events" 
 : 
  
 [ 
  
 { 
  
 "adIdentifiers" 
 : 
  
 { 
  
 "gclid" 
 : 
  
 "GCLID_1" 
  
 }, 
  
 "conversionValue" 
 : 
  
 30.03 
 , 
  
 "currency" 
 : 
  
 "USD" 
 , 
  
 "eventTimestamp" 
 : 
  
 "2025-06-10T20:07:01Z" 
 , 
  
 "transactionId" 
 : 
  
 "ABC798654321" 
 , 
  
 "eventSource" 
 : 
  
 "WEB" 
 , 
  
 "userData" 
 : 
  
 { 
  
 "userIdentifiers" 
 : 
  
 [ 
  
 { 
  
 "address" 
 : 
  
 { 
  
 "givenName" 
 : 
  
 "96D9632F363564CC3032521409CF22A852F2032EEC099ED5967C0D000CEC607A" 
 , 
  
 "familyName" 
 : 
  
 "DB98D2607EFFFA28AFF66975868BF54C075ECA7157E35064DCE08E20B85B1081" 
 , 
  
 "regionCode" 
 : 
  
 "US" 
 , 
  
 "postalCode" 
 : 
  
 "94045" 
  
 } 
  
 } 
  
 ] 
  
 }, 
  
 "userProperties" 
 : 
  
 { 
  
 "customerType" 
 : 
  
 "NEW" 
 , 
  
 "customerValueBucket" 
 : 
  
 "HIGH" 
  
 }, 
  
 "eventName" 
 : 
  
 "purchase" 
 , 
  
 "clientId" 
 : 
  
 "1234567890.1761581763" 
 , 
  
 "userId" 
 : 
  
 "user_ABC12345" 
 , 
  
 "additionalEventParameters" 
 : 
  
 [ 
  
 { 
  
 "parameterName" 
 : 
  
 "ad_unit_name" 
 , 
  
 "value" 
 : 
  
 "Banner_01" 
  
 } 
  
 ], 
  
 "cartData" 
 : 
  
 { 
  
 "transactionDiscount" 
 : 
  
 6.66 
 , 
  
 "items" 
 : 
  
 [ 
  
 { 
  
 "itemId" 
 : 
  
 "SKU_12345" 
 , 
  
 "quantity" 
 : 
  
 3 
 , 
  
 "unitPrice" 
 : 
  
 10.01 
 , 
  
 "additionalItemParameters" 
 : 
  
 [ 
  
 { 
  
 "parameterName" 
 : 
  
 "item_name" 
 , 
  
 "value" 
 : 
  
 "Stan and Friends Tee" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "affiliation" 
 , 
  
 "value" 
 : 
  
 "Google Merchandise Store" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "coupon" 
 , 
  
 "value" 
 : 
  
 "SUMMER_FUN" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "discount" 
 , 
  
 "value" 
 : 
  
 "2.22" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "index" 
 , 
  
 "value" 
 : 
  
 "0" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_brand" 
 , 
  
 "value" 
 : 
  
 "Google" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category" 
 , 
  
 "value" 
 : 
  
 "Apparel" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category2" 
 , 
  
 "value" 
 : 
  
 "Adult" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category3" 
 , 
  
 "value" 
 : 
  
 "Shirts" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category4" 
 , 
  
 "value" 
 : 
  
 "Crew" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category5" 
 , 
  
 "value" 
 : 
  
 "Short sleeve" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_list_id" 
 , 
  
 "value" 
 : 
  
 "related_products" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_list_name" 
 , 
  
 "value" 
 : 
  
 "Related Products" 
  
 } 
  
 ] 
  
 } 
  
 ] 
  
 } 
  
 }, 
  
 { 
  
 "adIdentifiers" 
 : 
  
 { 
  
 "gclid" 
 : 
  
 "GCLID_2" 
  
 }, 
  
 "conversionValue" 
 : 
  
 42.02 
 , 
  
 "currency" 
 : 
  
 "EUR" 
 , 
  
 "eventTimestamp" 
 : 
  
 "2025-06-11T04:42:33Z" 
 , 
  
 "transactionId" 
 : 
  
 "DEF999911111" 
 , 
  
 "eventSource" 
 : 
  
 "WEB" 
 , 
  
 "userData" 
 : 
  
 { 
  
 "userIdentifiers" 
 : 
  
 [ 
  
 { 
  
 "emailAddress" 
 : 
  
 "3E693CF7E5B67880BFF33B2D2626DADB7BF1D4BC737192E47CF8BAA89ACF2250" 
  
 }, 
  
 { 
  
 "emailAddress" 
 : 
  
 "223EBDA6F6889B1494551BA902D9D381DAF2F642BAE055888E96343D53E9F9C4" 
  
 }, 
  
 { 
  
 "address" 
 : 
  
 { 
  
 "givenName" 
 : 
  
 "2752B88686847FA5C86F47B94CE652B7B3F22A91C37617D451A4DB9AFA431450" 
 , 
  
 "familyName" 
 : 
  
 "6654977D57DDDD3C0329CA741B109EF6CD6430BEDD00008AAD213DF25683D77F" 
 , 
  
 "regionCode" 
 : 
  
 "PT" 
 , 
  
 "postalCode" 
 : 
  
 "1229-076" 
  
 } 
  
 } 
  
 ] 
  
 }, 
  
 "userProperties" 
 : 
  
 { 
  
 "customerType" 
 : 
  
 "RETURNING" 
  
 }, 
  
 "eventName" 
 : 
  
 "purchase" 
 , 
  
 "clientId" 
 : 
  
 "9876543210.1761582117" 
 , 
  
 "userId" 
 : 
  
 "user_DEF9876" 
 , 
  
 "additionalEventParameters" 
 : 
  
 [ 
  
 { 
  
 "parameterName" 
 : 
  
 "ad_unit_name" 
 , 
  
 "value" 
 : 
  
 "Banner_02" 
  
 } 
  
 ], 
  
 "cartData" 
 : 
  
 { 
  
 "transactionDiscount" 
 : 
  
 6.66 
 , 
  
 "items" 
 : 
  
 [ 
  
 { 
  
 "itemId" 
 : 
  
 "SKU_12346" 
 , 
  
 "quantity" 
 : 
  
 2 
 , 
  
 "unitPrice" 
 : 
  
 21.01 
 , 
  
 "additionalItemParameters" 
 : 
  
 [ 
  
 { 
  
 "parameterName" 
 : 
  
 "item_name" 
 , 
  
 "value" 
 : 
  
 "Google Grey Women's Tee" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "affiliation" 
 , 
  
 "value" 
 : 
  
 "Google Merchandise Store" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "coupon" 
 , 
  
 "value" 
 : 
  
 "SUMMER_FUN" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "discount" 
 , 
  
 "value" 
 : 
  
 "3.33" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "index" 
 , 
  
 "value" 
 : 
  
 "1" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_brand" 
 , 
  
 "value" 
 : 
  
 "Google" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category" 
 , 
  
 "value" 
 : 
  
 "Apparel" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category2" 
 , 
  
 "value" 
 : 
  
 "Adult" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category3" 
 , 
  
 "value" 
 : 
  
 "Shirts" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category4" 
 , 
  
 "value" 
 : 
  
 "Crew" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_category5" 
 , 
  
 "value" 
 : 
  
 "Short sleeve" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_list_id" 
 , 
  
 "value" 
 : 
  
 "related_products" 
  
 }, 
  
 { 
  
 "parameterName" 
 : 
  
 "item_list_name" 
 , 
  
 "value" 
 : 
  
 "Related Products" 
  
 } 
  
 ] 
  
 } 
  
 ] 
  
 } 
  
 } 
  
 ], 
  
 "validateOnly" 
 : 
  
 true 
 } 

.NET

 // Copyright 2025 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 
  
 System 
 . 
 Text 
 . 
 Json 
 ; 
 using 
  
 CommandLine 
 ; 
 using 
  
 Google 
 . 
 Ads 
 . 
 DataManager 
 . 
 Util 
 ; 
 using 
  
 Google 
 . 
 Ads 
 . 
 DataManager 
 . 
 V1 
 ; 
 using 
  
 Google 
 . 
 Protobuf 
 . 
 WellKnownTypes 
 ; 
 using 
  
 static 
  
 Google 
 . 
 Ads 
 . 
 DataManager 
 . 
 V1 
 . 
 ProductAccount 
 . 
 Types 
 ; 
 namespace 
  
 Google 
 . 
 Ads 
 . 
 DataManager 
 . 
 Samples 
 { 
  
 // <summary> 
  
 // Sends an <see cref="IngestEventsRequest" /> without using encryption. 
  
 // 
  
 // Event data is read from a data file. See the <c>events_1.json</c> file in the 
  
 // <c>sampledata</c> directory for an example. 
  
 // </summary> 
  
 public 
  
 class 
  
 IngestEvents 
  
 { 
  
 private 
  
 static 
  
 readonly 
  
 int 
  
 MaxEventsPerRequest 
  
 = 
  
 2 
 _000 
 ; 
  
 [ 
 Verb 
 ( 
 "ingest-events" 
 , 
  
 HelpText 
  
 = 
  
 "Sends an IngestEventsRequest without using encryption." 
 )] 
  
 public 
  
 class 
  
 Options 
  
 { 
  
 [ 
 Option 
 ( 
  
 "operatingAccountType" 
 , 
  
 Required 
  
 = 
  
 true 
 , 
  
 HelpText 
  
 = 
  
 "Account type of the operating account" 
  
 )] 
  
 public 
  
 AccountType 
  
 OperatingAccountType 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 [ 
 Option 
 ( 
  
 "operatingAccountId" 
 , 
  
 Required 
  
 = 
  
 true 
 , 
  
 HelpText 
  
 = 
  
 "ID of the operating account" 
  
 )] 
  
 public 
  
 string 
  
 OperatingAccountId 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 = 
  
 null 
 !; 
  
 [ 
 Option 
 ( 
  
 "loginAccountType" 
 , 
  
 Required 
  
 = 
  
 false 
 , 
  
 HelpText 
  
 = 
  
 "Account type of the login account" 
  
 )] 
  
 public 
  
 AccountType 
 ? 
  
 LoginAccountType 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 [ 
 Option 
 ( 
 "loginAccountId" 
 , 
  
 Required 
  
 = 
  
 false 
 , 
  
 HelpText 
  
 = 
  
 "ID of the login account" 
 )] 
  
 public 
  
 string 
 ? 
  
 LoginAccountId 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 [ 
 Option 
 ( 
  
 "linkedAccountProduct" 
 , 
  
 Required 
  
 = 
  
 false 
 , 
  
 HelpText 
  
 = 
  
 "Account type of the linked account" 
  
 )] 
  
 public 
  
 AccountType 
 ? 
  
 LinkedAccountType 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 [ 
 Option 
 ( 
 "linkedAccountId" 
 , 
  
 Required 
  
 = 
  
 false 
 , 
  
 HelpText 
  
 = 
  
 "ID of the linked account" 
 )] 
  
 public 
  
 string 
 ? 
  
 LinkedAccountId 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 [ 
 Option 
 ( 
  
 "conversionActionId" 
 , 
  
 Required 
  
 = 
  
 true 
 , 
  
 HelpText 
  
 = 
  
 "ID of the conversion action" 
  
 )] 
  
 public 
  
 string 
  
 ConversionActionId 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 = 
  
 null 
 !; 
  
 [ 
 Option 
 ( 
  
 "jsonFile" 
 , 
  
 Required 
  
 = 
  
 true 
 , 
  
 HelpText 
  
 = 
  
 "JSON file containing user data to ingest" 
  
 )] 
  
 public 
  
 string 
  
 JsonFile 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 = 
  
 null 
 !; 
  
 [ 
 Option 
 ( 
  
 "validateOnly" 
 , 
  
 Default 
  
 = 
  
 true 
 , 
  
 HelpText 
  
 = 
  
 "Whether to enable validateOnly on the request" 
  
 )] 
  
 public 
  
 bool 
  
 ValidateOnly 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 } 
  
 public 
  
 void 
  
 Run 
 ( 
 Options 
  
 options 
 ) 
  
 { 
  
 RunExample 
 ( 
  
 options 
 . 
 OperatingAccountType 
 , 
  
 options 
 . 
 OperatingAccountId 
 , 
  
 options 
 . 
 LoginAccountType 
 , 
  
 options 
 . 
 LoginAccountId 
 , 
  
 options 
 . 
 LinkedAccountType 
 , 
  
 options 
 . 
 LinkedAccountId 
 , 
  
 options 
 . 
 ConversionActionId 
 , 
  
 options 
 . 
 JsonFile 
 , 
  
 options 
 . 
 ValidateOnly 
  
 ); 
  
 } 
  
 private 
  
 void 
  
 RunExample 
 ( 
  
 AccountType 
  
 operatingAccountType 
 , 
  
 string 
  
 operatingAccountId 
 , 
  
 AccountType 
 ? 
  
 loginAccountType 
 , 
  
 string 
 ? 
  
 loginAccountId 
 , 
  
 AccountType 
 ? 
  
 linkedAccountType 
 , 
  
 string 
 ? 
  
 linkedAccountId 
 , 
  
 string 
  
 conversionActionId 
 , 
  
 string 
  
 jsonFile 
 , 
  
 bool 
  
 validateOnly 
  
 ) 
  
 { 
  
 if 
  
 ( 
 loginAccountId 
  
 == 
  
 null 
  
 ^ 
  
 loginAccountType 
  
 == 
  
 null 
 ) 
  
 { 
  
 throw 
  
 new 
  
 ArgumentException 
 ( 
  
 "Must specify either both or neither of login account ID and login account " 
  
 + 
  
 "type" 
  
 ); 
  
 } 
  
 if 
  
 ( 
 linkedAccountId 
  
 == 
  
 null 
  
 ^ 
  
 linkedAccountType 
  
 == 
  
 null 
 ) 
  
 { 
  
 throw 
  
 new 
  
 ArgumentException 
 ( 
  
 "Must specify either both or neither of linked account ID and linked account " 
  
 + 
  
 "type" 
  
 ); 
  
 } 
  
 // Reads member data from the data file. 
  
 List<EventRecord> 
  
 eventRecords 
  
 = 
  
 ReadEventData 
 ( 
 jsonFile 
 ); 
  
 // Gets an instance of the UserDataFormatter for normalizing and formatting the data. 
  
 UserDataFormatter 
  
 userDataFormatter 
  
 = 
  
 new 
  
 UserDataFormatter 
 (); 
  
 // Builds the events collection for the request. 
  
 var 
  
 events 
  
 = 
  
 new 
  
 List<Event> 
 (); 
  
 foreach 
  
 ( 
 var 
  
 eventRecord 
  
 in 
  
 eventRecords 
 ) 
  
 { 
  
 var 
  
 eventBuilder 
  
 = 
  
 new 
  
 Event 
 (); 
  
 try 
  
 { 
  
 eventBuilder 
 . 
 EventTimestamp 
  
 = 
  
 Timestamp 
 . 
 FromDateTime 
 ( 
  
 DateTime 
 . 
 Parse 
 ( 
 eventRecord 
 . 
 Timestamp 
  
 ?? 
  
 "" 
 ). 
 ToUniversalTime 
 () 
  
 ); 
  
 } 
  
 catch 
  
 ( 
 FormatException 
 ) 
  
 { 
  
 Console 
 . 
 WriteLine 
 ( 
  
 $ 
 "Skipping event with invalid timestamp: {eventRecord.Timestamp}" 
  
 ); 
  
 continue 
 ; 
  
 } 
  
 if 
  
 ( 
 string 
 . 
 IsNullOrEmpty 
 ( 
 eventRecord 
 . 
 TransactionId 
 )) 
  
 { 
  
 Console 
 . 
 WriteLine 
 ( 
 "Skipping event with no transaction ID" 
 ); 
  
 continue 
 ; 
  
 } 
  
 eventBuilder 
 . 
 TransactionId 
  
 = 
  
 eventRecord 
 . 
 TransactionId 
 ; 
  
 if 
  
 (! 
 string 
 . 
 IsNullOrEmpty 
 ( 
 eventRecord 
 . 
 EventSource 
 )) 
  
 { 
  
 if 
  
 ( 
  
 System 
 . 
 Enum 
 . 
 TryParse 
 ( 
  
 eventRecord 
 . 
 EventSource 
 , 
  
 true 
 , 
  
 out 
  
 EventSource 
  
 eventSource 
  
 ) 
  
 ) 
  
 { 
  
 eventBuilder 
 . 
 EventSource 
  
 = 
  
 eventSource 
 ; 
  
 } 
  
 else 
  
 { 
  
 Console 
 . 
 WriteLine 
 ( 
  
 $ 
 "Skipping event with invalid event source: {eventRecord.EventSource}" 
  
 ); 
  
 continue 
 ; 
  
 } 
  
 } 
  
 if 
  
 (! 
 string 
 . 
 IsNullOrEmpty 
 ( 
 eventRecord 
 . 
 Gclid 
 )) 
  
 { 
  
 eventBuilder 
 . 
 AdIdentifiers 
  
 = 
  
 new 
  
 AdIdentifiers 
  
 { 
  
 Gclid 
  
 = 
  
 eventRecord 
 . 
 Gclid 
  
 }; 
  
 } 
  
 if 
  
 (! 
 string 
 . 
 IsNullOrEmpty 
 ( 
 eventRecord 
 . 
 Currency 
 )) 
  
 { 
  
 eventBuilder 
 . 
 Currency 
  
 = 
  
 eventRecord 
 . 
 Currency 
 ; 
  
 } 
  
 if 
  
 ( 
 eventRecord 
 . 
 Value 
 . 
 HasValue 
 ) 
  
 { 
  
 eventBuilder 
 . 
 ConversionValue 
  
 = 
  
 eventRecord 
 . 
 Value 
 . 
 Value 
 ; 
  
 } 
  
 var 
  
 userDataBuilder 
  
 = 
  
 new 
  
 UserData 
 (); 
  
 // Adds a UserIdentifier for each valid email address for the eventRecord. 
  
 if 
  
 ( 
 eventRecord 
 . 
 Emails 
  
 != 
  
 null 
 ) 
  
 { 
  
 foreach 
  
 ( 
 var 
  
 email 
  
 in 
  
 eventRecord 
 . 
 Emails 
 ) 
  
 { 
  
 try 
  
 { 
  
 string 
  
 preparedEmail 
  
 = 
  
 userDataFormatter 
 . 
 ProcessEmailAddress 
 ( 
  
 email 
 , 
  
 UserDataFormatter 
 . 
 Encoding 
 . 
 Hex 
  
 ); 
  
 // Adds an email address identifier with the encoded email hash. 
  
 userDataBuilder 
 . 
 UserIdentifiers 
 . 
 Add 
 ( 
  
 new 
  
 UserIdentifier 
  
 { 
  
 EmailAddress 
  
 = 
  
 preparedEmail 
  
 } 
  
 ); 
  
 } 
  
 catch 
  
 ( 
 ArgumentException 
 ) 
  
 { 
  
 // Skips invalid input. 
  
 continue 
 ; 
  
 } 
  
 } 
  
 } 
  
 // Adds a UserIdentifier for each valid phone number for the eventRecord. 
  
 if 
  
 ( 
 eventRecord 
 . 
 PhoneNumbers 
  
 != 
  
 null 
 ) 
  
 { 
  
 foreach 
  
 ( 
 var 
  
 phoneNumber 
  
 in 
  
 eventRecord 
 . 
 PhoneNumbers 
 ) 
  
 { 
  
 try 
  
 { 
  
 string 
  
 preparedPhoneNumber 
  
 = 
  
 userDataFormatter 
 . 
 ProcessPhoneNumber 
 ( 
  
 phoneNumber 
 , 
  
 UserDataFormatter 
 . 
 Encoding 
 . 
 Hex 
  
 ); 
  
 // Adds a phone number identifier with the encoded phone hash. 
  
 userDataBuilder 
 . 
 UserIdentifiers 
 . 
 Add 
 ( 
  
 new 
  
 UserIdentifier 
  
 { 
  
 PhoneNumber 
  
 = 
  
 preparedPhoneNumber 
  
 } 
  
 ); 
  
 } 
  
 catch 
  
 ( 
 ArgumentException 
 ) 
  
 { 
  
 // Skips invalid input. 
  
 continue 
 ; 
  
 } 
  
 } 
  
 } 
  
 if 
  
 ( 
 userDataBuilder 
 . 
 UserIdentifiers 
 . 
 Any 
 ()) 
  
 { 
  
 eventBuilder 
 . 
 UserData 
  
 = 
  
 userDataBuilder 
 ; 
  
 } 
  
 events 
 . 
 Add 
 ( 
 eventBuilder 
 ); 
  
 } 
  
 // Builds the Destination for the request. 
  
 var 
  
 destinationBuilder 
  
 = 
  
 new 
  
 Destination 
  
 { 
  
 OperatingAccount 
  
 = 
  
 new 
  
 ProductAccount 
  
 { 
  
 AccountType 
  
 = 
  
 operatingAccountType 
 , 
  
 AccountId 
  
 = 
  
 operatingAccountId 
 , 
  
 }, 
  
 ProductDestinationId 
  
 = 
  
 conversionActionId 
 , 
  
 }; 
  
 if 
  
 ( 
 loginAccountType 
 . 
 HasValue 
 && 
 loginAccountId 
  
 != 
  
 null 
 ) 
  
 { 
  
 destinationBuilder 
 . 
 LoginAccount 
  
 = 
  
 new 
  
 ProductAccount 
  
 { 
  
 AccountType 
  
 = 
  
 loginAccountType 
 . 
 Value 
 , 
  
 AccountId 
  
 = 
  
 loginAccountId 
 , 
  
 }; 
  
 } 
  
 if 
  
 ( 
 linkedAccountType 
 . 
 HasValue 
 && 
 linkedAccountId 
  
 != 
  
 null 
 ) 
  
 { 
  
 destinationBuilder 
 . 
 LinkedAccount 
  
 = 
  
 new 
  
 ProductAccount 
  
 { 
  
 AccountType 
  
 = 
  
 linkedAccountType 
 . 
 Value 
 , 
  
 AccountId 
  
 = 
  
 linkedAccountId 
 , 
  
 }; 
  
 } 
  
 IngestionServiceClient 
  
 ingestionServiceClient 
  
 = 
  
 IngestionServiceClient 
 . 
 Create 
 (); 
  
 int 
  
 requestCount 
  
 = 
  
 0 
 ; 
  
 // Batches requests to send up to the maximum number of events per request. 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 events 
 . 
 Count 
 ; 
  
 i 
  
 += 
  
 MaxEventsPerRequest 
 ) 
  
 { 
  
 IEnumerable<Event> 
  
 batch 
  
 = 
  
 events 
 . 
 Skip 
 ( 
 i 
 ). 
 Take 
 ( 
 MaxEventsPerRequest 
 ); 
  
 requestCount 
 ++ 
 ; 
  
 var 
  
 request 
  
 = 
  
 new 
  
 IngestEventsRequest 
  
 { 
  
 Destinations 
  
 = 
  
 { 
  
 destinationBuilder 
  
 }, 
  
 // Adds events from the current batch. 
  
 Events 
  
 = 
  
 { 
  
 batch 
  
 }, 
  
 Consent 
  
 = 
  
 new 
  
 Consent 
  
 { 
  
 AdPersonalization 
  
 = 
  
 ConsentStatus 
 . 
 ConsentGranted 
 , 
  
 AdUserData 
  
 = 
  
 ConsentStatus 
 . 
 ConsentGranted 
 , 
  
 }, 
  
 // Sets validate_only. If true, then the Data Manager API only validates the 
  
 // request but doesn't apply changes. 
  
 ValidateOnly 
  
 = 
  
 validateOnly 
 , 
  
 Encoding 
  
 = 
  
 V1 
 . 
 Encoding 
 . 
 Hex 
 , 
  
 }; 
  
 // Sends the data to the Data Manager API. 
  
 IngestEventsResponse 
  
 response 
  
 = 
  
 ingestionServiceClient 
 . 
 IngestEvents 
 ( 
 request 
 ); 
  
 Console 
 . 
 WriteLine 
 ( 
 $ 
 "Response for request #{requestCount}:\n{response}" 
 ); 
  
 } 
  
 Console 
 . 
 WriteLine 
 ( 
 $ 
 "# of requests sent: {requestCount}" 
 ); 
  
 } 
  
 private 
  
 class 
  
 EventRecord 
  
 { 
  
 public 
  
 List<string> 
 ? 
  
 Emails 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 public 
  
 List<string> 
 ? 
  
 PhoneNumbers 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 public 
  
 string 
 ? 
  
 Timestamp 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 public 
  
 string 
 ? 
  
 TransactionId 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 public 
  
 string 
 ? 
  
 EventSource 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 public 
  
 double 
 ? 
  
 Value 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 public 
  
 string 
 ? 
  
 Currency 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 public 
  
 string 
 ? 
  
 Gclid 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 } 
  
 private 
  
 List<EventRecord> 
  
 ReadEventData 
 ( 
 string 
  
 jsonFile 
 ) 
  
 { 
  
 string 
  
 jsonString 
  
 = 
  
 File 
 . 
 ReadAllText 
 ( 
 jsonFile 
 ); 
  
 var 
  
 options 
  
 = 
  
 new 
  
 JsonSerializerOptions 
  
 { 
  
 PropertyNameCaseInsensitive 
  
 = 
  
 true 
  
 }; 
  
 return 
  
 JsonSerializer 
 . 
 Deserialize<List<EventRecord> 
> ( 
 jsonString 
 , 
  
 options 
 ) 
  
 ?? 
  
 new 
  
 List<EventRecord> 
 (); 
  
 } 
  
 } 
 } 
  

Java

 // Copyright 2025 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. 
 package 
  
 com.google.ads.datamanager.samples 
 ; 
 import 
  
 com.beust.jcommander.Parameter 
 ; 
 import 
  
 com.google.ads.datamanager.samples.common.BaseParamsConfig 
 ; 
 import 
  
 com.google.ads.datamanager.util.UserDataFormatter 
 ; 
 import 
  
 com.google.ads.datamanager.util.UserDataFormatter.Encoding 
 ; 
 import 
  
 com.google.ads.datamanager.v1.AdIdentifiers 
 ; 
 import 
  
 com.google.ads.datamanager.v1.Consent 
 ; 
 import 
  
 com.google.ads.datamanager.v1.ConsentStatus 
 ; 
 import 
  
 com.google.ads.datamanager.v1.Destination 
 ; 
 import 
  
 com.google.ads.datamanager.v1.Event 
 ; 
 import 
  
 com.google.ads.datamanager.v1.EventSource 
 ; 
 import 
  
 com.google.ads.datamanager.v1.IngestEventsRequest 
 ; 
 import 
  
 com.google.ads.datamanager.v1.IngestEventsResponse 
 ; 
 import 
  
 com.google.ads.datamanager.v1.IngestionServiceClient 
 ; 
 import 
  
 com.google.ads.datamanager.v1.ProductAccount 
 ; 
 import 
  
 com.google.ads.datamanager.v1.ProductAccount.AccountType 
 ; 
 import 
  
 com.google.ads.datamanager.v1.UserData 
 ; 
 import 
  
 com.google.ads.datamanager.v1.UserIdentifier 
 ; 
 import 
  
 com.google.common.base.Strings 
 ; 
 import 
  
 com.google.common.collect.Lists 
 ; 
 import 
  
 com.google.common.reflect.TypeToken 
 ; 
 import 
  
 com.google.gson.GsonBuilder 
 ; 
 import 
  
 com.google.protobuf.util.Timestamps 
 ; 
 import 
  
 java.io.BufferedReader 
 ; 
 import 
  
 java.io.IOException 
 ; 
 import 
  
 java.lang.reflect.Type 
 ; 
 import 
  
 java.nio.charset.StandardCharsets 
 ; 
 import 
  
 java.nio.file.Files 
 ; 
 import 
  
 java.nio.file.Paths 
 ; 
 import 
  
 java.text.ParseException 
 ; 
 import 
  
 java.util.ArrayList 
 ; 
 import 
  
 java.util.List 
 ; 
 import 
  
 java.util.logging.Logger 
 ; 
 /** 
 * Sends an {@link IngestEventsRequest} without using encryption. 
 * 
 * <p>Event data is read from a data file. See the {@code events_1.json} file in the {@code 
 * resources/sampledata} directory for a sample file. 
 */ 
 public 
  
 class 
 IngestEvents 
  
 { 
  
 private 
  
 static 
  
 final 
  
 Logger 
  
 LOGGER 
  
 = 
  
 Logger 
 . 
 getLogger 
 ( 
 IngestEvents 
 . 
 class 
 . 
 getName 
 ()); 
  
 /** The maximum number of events allowed per request. */ 
  
 private 
  
 static 
  
 final 
  
 int 
  
 MAX_EVENTS_PER_REQUEST 
  
 = 
  
 2_000 
 ; 
  
 private 
  
 static 
  
 final 
  
 class 
 ParamsConfig 
  
 extends 
  
 BaseParamsConfig<ParamsConfig> 
  
 { 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--operatingAccountType" 
 , 
  
 required 
  
 = 
  
 true 
 , 
  
 description 
  
 = 
  
 "Account type of the operating account" 
 ) 
  
 AccountType 
  
 operatingAccountType 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--operatingAccountId" 
 , 
  
 required 
  
 = 
  
 true 
 , 
  
 description 
  
 = 
  
 "ID of the operating account" 
 ) 
  
 String 
  
 operatingAccountId 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--loginAccountType" 
 , 
  
 required 
  
 = 
  
 false 
 , 
  
 description 
  
 = 
  
 "Account type of the login account" 
 ) 
  
 AccountType 
  
 loginAccountType 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--loginAccountId" 
 , 
  
 required 
  
 = 
  
 false 
 , 
  
 description 
  
 = 
  
 "ID of the login account" 
 ) 
  
 String 
  
 loginAccountId 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--linkedAccountType" 
 , 
  
 required 
  
 = 
  
 false 
 , 
  
 description 
  
 = 
  
 "Account type of the linked account" 
 ) 
  
 AccountType 
  
 linkedAccountType 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--linkedAccountId" 
 , 
  
 required 
  
 = 
  
 false 
 , 
  
 description 
  
 = 
  
 "ID of the linked account" 
 ) 
  
 String 
  
 linkedAccountId 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--conversionActionId" 
 , 
  
 required 
  
 = 
  
 true 
 , 
  
 description 
  
 = 
  
 "ID of the conversion action" 
 ) 
  
 String 
  
 conversionActionId 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--jsonFile" 
 , 
  
 required 
  
 = 
  
 true 
 , 
  
 description 
  
 = 
  
 "JSON file containing user data to ingest" 
 ) 
  
 String 
  
 jsonFile 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--validateOnly" 
 , 
  
 required 
  
 = 
  
 false 
 , 
  
 arity 
  
 = 
  
 1 
 , 
  
 description 
  
 = 
  
 "Whether to enable validateOnly on the request" 
 ) 
  
 boolean 
  
 validateOnly 
  
 = 
  
 true 
 ; 
  
 } 
  
 public 
  
 static 
  
 void 
  
 main 
 ( 
 String 
 [] 
  
 args 
 ) 
  
 throws 
  
 IOException 
  
 { 
  
 ParamsConfig 
  
 paramsConfig 
  
 = 
  
 new 
  
 ParamsConfig 
 (). 
 parseOrExit 
 ( 
 args 
 ); 
  
 if 
  
 (( 
 paramsConfig 
 . 
 loginAccountId 
  
 == 
  
 null 
 ) 
  
 != 
  
 ( 
 paramsConfig 
 . 
 loginAccountType 
  
 == 
  
 null 
 )) 
  
 { 
  
 throw 
  
 new 
  
 IllegalArgumentException 
 ( 
  
 "Must specify either both or neither of login account ID and login account type" 
 ); 
  
 } 
  
 if 
  
 (( 
 paramsConfig 
 . 
 linkedAccountId 
  
 == 
  
 null 
 ) 
  
 != 
  
 ( 
 paramsConfig 
 . 
 linkedAccountType 
  
 == 
  
 null 
 )) 
  
 { 
  
 throw 
  
 new 
  
 IllegalArgumentException 
 ( 
  
 "Must specify either both or neither of linked account ID and linked account type" 
 ); 
  
 } 
  
 new 
  
 IngestEvents 
 (). 
 runExample 
 ( 
 paramsConfig 
 ); 
  
 } 
  
 /** 
 * Runs the example. This sample assumes that the login and operating account are the same. 
 * 
 * @param params the parameters for the example 
 */ 
  
 private 
  
 void 
  
 runExample 
 ( 
 ParamsConfig 
  
 params 
 ) 
  
 throws 
  
 IOException 
  
 { 
  
 // Reads event data from the JSON file. 
  
 List<EventRecord> 
  
 eventRecords 
  
 = 
  
 readEventData 
 ( 
 params 
 . 
 jsonFile 
 ); 
  
 // Gets an instance of the UserDataFormatter for normalizing and formatting the data. 
  
 UserDataFormatter 
  
 userDataFormatter 
  
 = 
  
 UserDataFormatter 
 . 
 create 
 (); 
  
 // Builds the events collection for the request. 
  
 List<Event> 
  
 events 
  
 = 
  
 new 
  
 ArrayList 
<> (); 
  
 for 
  
 ( 
 EventRecord 
  
 eventRecord 
  
 : 
  
 eventRecords 
 ) 
  
 { 
  
 Event 
 . 
 Builder 
  
 eventBuilder 
  
 = 
  
 Event 
 . 
 newBuilder 
 (); 
  
 try 
  
 { 
  
 eventBuilder 
 . 
 setEventTimestamp 
 ( 
 Timestamps 
 . 
 parse 
 ( 
 eventRecord 
 . 
 timestamp 
 )); 
  
 } 
  
 catch 
  
 ( 
 ParseException 
  
 pe 
 ) 
  
 { 
  
 LOGGER 
 . 
 warning 
 ( 
  
 () 
  
 - 
>  
 String 
 . 
 format 
 ( 
 "Skipping event with invalid timestamp: %s" 
 , 
  
 eventRecord 
 . 
 timestamp 
 )); 
  
 continue 
 ; 
  
 } 
  
 if 
  
 ( 
 Strings 
 . 
 isNullOrEmpty 
 ( 
 eventRecord 
 . 
 transactionId 
 )) 
  
 { 
  
 LOGGER 
 . 
 warning 
 ( 
 "Skipping event with no transaction ID" 
 ); 
  
 continue 
 ; 
  
 } 
  
 eventBuilder 
 . 
 setTransactionId 
 ( 
 eventRecord 
 . 
 transactionId 
 ); 
  
 if 
  
 ( 
 ! 
 Strings 
 . 
 isNullOrEmpty 
 ( 
 eventRecord 
 . 
 eventSource 
 )) 
  
 { 
  
 try 
  
 { 
  
 eventBuilder 
 . 
 setEventSource 
 ( 
 EventSource 
 . 
 valueOf 
 ( 
 eventRecord 
 . 
 eventSource 
 )); 
  
 } 
  
 catch 
  
 ( 
 IllegalArgumentException 
  
 iae 
 ) 
  
 { 
  
 LOGGER 
 . 
 warning 
 ( 
 "Skipping event with invalid event source: " 
  
 + 
  
 eventRecord 
 . 
 eventSource 
 ); 
  
 continue 
 ; 
  
 } 
  
 } 
  
 if 
  
 ( 
 ! 
 Strings 
 . 
 isNullOrEmpty 
 ( 
 eventRecord 
 . 
 gclid 
 )) 
  
 { 
  
 eventBuilder 
 . 
 setAdIdentifiers 
 ( 
 AdIdentifiers 
 . 
 newBuilder 
 (). 
 setGclid 
 ( 
 eventRecord 
 . 
 gclid 
 )); 
  
 } 
  
 if 
  
 ( 
 ! 
 Strings 
 . 
 isNullOrEmpty 
 ( 
 eventRecord 
 . 
 currency 
 )) 
  
 { 
  
 eventBuilder 
 . 
 setCurrency 
 ( 
 eventRecord 
 . 
 currency 
 ); 
  
 } 
  
 if 
  
 ( 
 eventRecord 
 . 
 value 
  
 != 
  
 null 
 ) 
  
 { 
  
 eventBuilder 
 . 
 setConversionValue 
 ( 
 eventRecord 
 . 
 value 
 ); 
  
 } 
  
 UserData 
 . 
 Builder 
  
 userDataBuilder 
  
 = 
  
 UserData 
 . 
 newBuilder 
 (); 
  
 // Adds a UserIdentifier for each valid email address for the eventRecord. 
  
 if 
  
 ( 
 eventRecord 
 . 
 emails 
  
 != 
  
 null 
 ) 
  
 { 
  
 for 
  
 ( 
 String 
  
 email 
  
 : 
  
 eventRecord 
 . 
 emails 
 ) 
  
 { 
  
 String 
  
 preparedEmail 
 ; 
  
 try 
  
 { 
  
 preparedEmail 
  
 = 
  
 userDataFormatter 
 . 
 processEmailAddress 
 ( 
 email 
 , 
  
 Encoding 
 . 
 HEX 
 ); 
  
 } 
  
 catch 
  
 ( 
 IllegalArgumentException 
  
 iae 
 ) 
  
 { 
  
 // Skips invalid input. 
  
 continue 
 ; 
  
 } 
  
 // Sets the email address identifier to the encoded email hash. 
  
 userDataBuilder 
 . 
 addUserIdentifiers 
 ( 
  
 UserIdentifier 
 . 
 newBuilder 
 (). 
 setEmailAddress 
 ( 
 preparedEmail 
 )); 
  
 } 
  
 } 
  
 // Adds a UserIdentifier for each valid phone number for the eventRecord. 
  
 if 
  
 ( 
 eventRecord 
 . 
 phoneNumbers 
  
 != 
  
 null 
 ) 
  
 { 
  
 for 
  
 ( 
 String 
  
 phoneNumber 
  
 : 
  
 eventRecord 
 . 
 phoneNumbers 
 ) 
  
 { 
  
 String 
  
 preparedPhoneNumber 
 ; 
  
 try 
  
 { 
  
 preparedPhoneNumber 
  
 = 
  
 userDataFormatter 
 . 
 processPhoneNumber 
 ( 
 phoneNumber 
 , 
  
 Encoding 
 . 
 HEX 
 ); 
  
 } 
  
 catch 
  
 ( 
 IllegalArgumentException 
  
 iae 
 ) 
  
 { 
  
 // Skips invalid input. 
  
 continue 
 ; 
  
 } 
  
 // Sets the phone number identifier to the encoded phone number hash. 
  
 userDataBuilder 
 . 
 addUserIdentifiers 
 ( 
  
 UserIdentifier 
 . 
 newBuilder 
 (). 
 setPhoneNumber 
 ( 
 preparedPhoneNumber 
 )); 
  
 } 
  
 } 
  
 if 
  
 ( 
 userDataBuilder 
 . 
 getUserIdentifiersCount 
 () 
 > 
 0 
 ) 
  
 { 
  
 eventBuilder 
 . 
 setUserData 
 ( 
 userDataBuilder 
 ); 
  
 } 
  
 events 
 . 
 add 
 ( 
 eventBuilder 
 . 
 build 
 ()); 
  
 } 
  
 // Builds the Destination for the request. 
  
 Destination 
 . 
 Builder 
  
 destinationBuilder 
  
 = 
  
 Destination 
 . 
 newBuilder 
 () 
  
 . 
 setOperatingAccount 
 ( 
  
 ProductAccount 
 . 
 newBuilder 
 () 
  
 . 
 setAccountType 
 ( 
 params 
 . 
 operatingAccountType 
 ) 
  
 . 
 setAccountId 
 ( 
 params 
 . 
 operatingAccountId 
 )) 
  
 . 
 setProductDestinationId 
 ( 
 params 
 . 
 conversionActionId 
 ); 
  
 if 
  
 ( 
 params 
 . 
 loginAccountType 
  
 != 
  
 null 
 && 
 params 
 . 
 loginAccountId 
  
 != 
  
 null 
 ) 
  
 { 
  
 destinationBuilder 
 . 
 setLoginAccount 
 ( 
  
 ProductAccount 
 . 
 newBuilder 
 () 
  
 . 
 setAccountType 
 ( 
 params 
 . 
 loginAccountType 
 ) 
  
 . 
 setAccountId 
 ( 
 params 
 . 
 loginAccountId 
 )); 
  
 } 
  
 if 
  
 ( 
 params 
 . 
 linkedAccountType 
  
 != 
  
 null 
 && 
 params 
 . 
 linkedAccountId 
  
 != 
  
 null 
 ) 
  
 { 
  
 destinationBuilder 
 . 
 setLinkedAccount 
 ( 
  
 ProductAccount 
 . 
 newBuilder 
 () 
  
 . 
 setAccountType 
 ( 
 params 
 . 
 linkedAccountType 
 ) 
  
 . 
 setAccountId 
 ( 
 params 
 . 
 linkedAccountId 
 )); 
  
 } 
  
 try 
  
 ( 
 IngestionServiceClient 
  
 ingestionServiceClient 
  
 = 
  
 IngestionServiceClient 
 . 
 create 
 ()) 
  
 { 
  
 int 
  
 requestCount 
  
 = 
  
 0 
 ; 
  
 // Batches requests to send up to the maximum number of events per request. 
  
 for 
  
 ( 
 List<Event> 
  
 eventsBatch 
  
 : 
  
 Lists 
 . 
 partition 
 ( 
 events 
 , 
  
 MAX_EVENTS_PER_REQUEST 
 )) 
  
 { 
  
 requestCount 
 ++ 
 ; 
  
 // Builds the request. 
  
 IngestEventsRequest 
  
 request 
  
 = 
  
 IngestEventsRequest 
 . 
 newBuilder 
 () 
  
 . 
 addDestinations 
 ( 
 destinationBuilder 
 ) 
  
 // Adds events from the current batch. 
  
 . 
 addAllEvents 
 ( 
 eventsBatch 
 ) 
  
 . 
 setConsent 
 ( 
  
 Consent 
 . 
 newBuilder 
 () 
  
 . 
 setAdPersonalization 
 ( 
 ConsentStatus 
 . 
 CONSENT_GRANTED 
 ) 
  
 . 
 setAdUserData 
 ( 
 ConsentStatus 
 . 
 CONSENT_GRANTED 
 )) 
  
 // Sets validate_only. If true, then the Data Manager API only validates the request 
  
 // but doesn't apply changes. 
  
 . 
 setValidateOnly 
 ( 
 params 
 . 
 validateOnly 
 ) 
  
 // Sets encoding to match the encoding used. 
  
 . 
 setEncoding 
 ( 
 com 
 . 
 google 
 . 
 ads 
 . 
 datamanager 
 . 
 v1 
 . 
 Encoding 
 . 
 HEX 
 ) 
  
 . 
 build 
 (); 
  
 LOGGER 
 . 
 info 
 (() 
  
 - 
>  
 String 
 . 
 format 
 ( 
 "Request:%n%s" 
 , 
  
 request 
 )); 
  
 IngestEventsResponse 
  
 response 
  
 = 
  
 ingestionServiceClient 
 . 
 ingestEvents 
 ( 
 request 
 ); 
  
 LOGGER 
 . 
 info 
 ( 
 String 
 . 
 format 
 ( 
 "Response for request #:%n%s" 
 , 
  
 requestCount 
 , 
  
 response 
 )); 
  
 } 
  
 LOGGER 
 . 
 info 
 ( 
 "# of requests sent: " 
  
 + 
  
 requestCount 
 ); 
  
 } 
  
 } 
  
 /** Data object for a single row of input data. */ 
  
 @SuppressWarnings 
 ( 
 "unused" 
 ) 
  
 private 
  
 static 
  
 class 
 EventRecord 
  
 { 
  
 private 
  
 List<String> 
  
 emails 
 ; 
  
 private 
  
 List<String> 
  
 phoneNumbers 
 ; 
  
 private 
  
 String 
  
 timestamp 
 ; 
  
 private 
  
 String 
  
 transactionId 
 ; 
  
 private 
  
 String 
  
 eventSource 
 ; 
  
 private 
  
 Double 
  
 value 
 ; 
  
 private 
  
 String 
  
 currency 
 ; 
  
 private 
  
 String 
  
 gclid 
 ; 
  
 } 
  
 /** Reads the data file and parses each line into a {@link EventRecord} object. */ 
  
 private 
  
 List<EventRecord> 
  
 readEventData 
 ( 
 String 
  
 jsonFile 
 ) 
  
 throws 
  
 IOException 
  
 { 
  
 try 
  
 ( 
 BufferedReader 
  
 jsonReader 
  
 = 
  
 Files 
 . 
 newBufferedReader 
 ( 
 Paths 
 . 
 get 
 ( 
 jsonFile 
 ), 
  
 StandardCharsets 
 . 
 UTF_8 
 )) 
  
 { 
  
 // Define the type for Gson to deserialize into (List of EventRecord objects) 
  
 Type 
  
 recordListType 
  
 = 
  
 new 
  
 TypeToken<ArrayList<EventRecord> 
> () 
  
 {}. 
 getType 
 (); 
  
 // Parse the JSON string from the file into a List of EventRecord objects 
  
 return 
  
 new 
  
 GsonBuilder 
 (). 
 create 
 (). 
 fromJson 
 ( 
 jsonReader 
 , 
  
 recordListType 
 ); 
  
 } 
  
 } 
 } 
  

Node

 #!/usr/bin/env node 
 // Copyright 2025 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. 
 'use strict' 
 ; 
 import 
  
 { 
 IngestionServiceClient 
 } 
  
 from 
  
 '@google-ads/datamanager' 
 ; 
 import 
  
 { 
 protos 
 } 
  
 from 
  
 '@google-ads/datamanager' 
 ; 
 const 
  
 { 
  
 Event 
 : 
  
 DataManagerEvent 
 , 
  
 Destination 
 , 
  
 Encoding 
 : 
  
 DataManagerEncoding 
 , 
  
 EventSource 
 , 
  
 Consent 
 , 
  
 ConsentStatus 
 , 
  
 IngestEventsRequest 
 , 
  
 ProductAccount 
 , 
  
 UserData 
 , 
  
 UserIdentifier 
 , 
 } 
  
 = 
  
 protos 
 . 
 google 
 . 
 ads 
 . 
 datamanager 
 . 
 v1 
 ; 
 import 
  
 { 
 UserDataFormatter 
 , 
  
 Encoding 
 } 
  
 from 
  
 '@google-ads/data-manager-util' 
 ; 
 import 
  
 * 
  
 as 
  
 fs 
  
 from 
  
 'fs' 
 ; 
 import 
  
 * 
  
 as 
  
 yargs 
  
 from 
  
 'yargs' 
 ; 
 const 
  
 MAX_EVENTS_PER_REQUEST 
  
 = 
  
 10000 
 ; 
 interface 
  
 Arguments 
  
 { 
  
 operating_account_type 
 : 
  
 string 
 ; 
  
 operating_account_id 
 : 
  
 string 
 ; 
  
 conversion_action_id 
 : 
  
 string 
 ; 
  
 json_file 
 : 
  
 string 
 ; 
  
 validate_only 
 : 
  
 boolean 
 ; 
  
 login_account_type 
 ?: 
  
 string 
 ; 
  
 login_account_id 
 ?: 
  
 string 
 ; 
  
 linked_account_type 
 ?: 
  
 string 
 ; 
  
 linked_account_id 
 ?: 
  
 string 
 ; 
  
 [ 
 x 
 : 
  
 string 
 ] 
 : 
  
 unknown 
 ; 
 } 
 interface 
  
 EventRow 
  
 { 
  
 timestamp 
 : 
  
 string 
 ; 
  
 transactionId 
 : 
  
 string 
 ; 
  
 eventSource 
 ?: 
  
 string 
 ; 
  
 gclid 
 ?: 
  
 string 
 ; 
  
 currency 
 ?: 
  
 string 
 ; 
  
 value 
 ?: 
  
 number 
 ; 
  
 emails 
 ?: 
  
 string 
 []; 
  
 phoneNumbers 
 ?: 
  
 string 
 []; 
 } 
 /** 
 * The main function for the IngestEvents sample. 
 */ 
 async 
  
 function 
  
 main 
 () 
  
 { 
  
 const 
  
 argv 
 : 
  
 Arguments 
  
 = 
  
 yargs 
  
 . 
 option 
 ( 
 'operating_account_type' 
 , 
  
 { 
  
 describe 
 : 
  
 'The account type of the operating account.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 required 
 : 
  
 true 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'operating_account_id' 
 , 
  
 { 
  
 describe 
 : 
  
 'The ID of the operating account.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 required 
 : 
  
 true 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'conversion_action_id' 
 , 
  
 { 
  
 describe 
 : 
  
 'The ID of the conversion action.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 required 
 : 
  
 true 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'json_file' 
 , 
  
 { 
  
 describe 
 : 
  
 'JSON file containing user data to ingest.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 required 
 : 
  
 true 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'validate_only' 
 , 
  
 { 
  
 describe 
 : 
  
 'Whether to enable validate_only on the request.' 
 , 
  
 type 
 : 
  
 'boolean' 
 , 
  
 default 
 : 
  
 true 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'login_account_type' 
 , 
  
 { 
  
 describe 
 : 
  
 'The account type of the login account.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'login_account_id' 
 , 
  
 { 
  
 describe 
 : 
  
 'The ID of the login account.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'linked_account_type' 
 , 
  
 { 
  
 describe 
 : 
  
 'The account type of the linked account.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'linked_account_id' 
 , 
  
 { 
  
 describe 
 : 
  
 'The ID of the linked account.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'config' 
 , 
  
 { 
  
 describe 
 : 
  
 'Path to a JSON file with arguments.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 }) 
  
 . 
 config 
 ( 
 'config' 
 ) 
  
 . 
 check 
 (( 
 args 
 : 
  
 Arguments 
 ) 
  
 = 
>  
 { 
  
 if 
  
 ( 
  
 ( 
 args 
 . 
 login_account_type 
 && 
 ! 
 args 
 . 
 login_account_id 
 ) 
  
 || 
  
 ( 
 ! 
 args 
 . 
 login_account_type 
 && 
 args 
 . 
 login_account_id 
 ) 
  
 ) 
  
 { 
  
 throw 
  
 new 
  
 Error 
 ( 
  
 'Must specify either both or neither of login account type ' 
  
 + 
  
 'and login account ID' 
 , 
  
 ); 
  
 } 
  
 if 
  
 ( 
  
 ( 
 args 
 . 
 linked_account_type 
 && 
 ! 
 args 
 . 
 linked_account_id 
 ) 
  
 || 
  
 ( 
 ! 
 args 
 . 
 linked_account_type 
 && 
 args 
 . 
 linked_account_id 
 ) 
  
 ) 
  
 { 
  
 throw 
  
 new 
  
 Error 
 ( 
  
 'Must specify either both or neither of linked account type ' 
  
 + 
  
 'and linked account ID' 
 , 
  
 ); 
  
 } 
  
 return 
  
 true 
 ; 
  
 }) 
  
 . 
 parseSync 
 (); 
  
 // Reads event data from the JSON file. 
  
 const 
  
 eventRows 
 : 
  
 EventRow 
 [] 
  
 = 
  
 readEventDataFile 
 ( 
 argv 
 . 
 json_file 
 ); 
  
 // Builds the events collection for the request. 
  
 const 
  
 events 
  
 = 
  
 []; 
  
 const 
  
 formatter 
  
 = 
  
 new 
  
 UserDataFormatter 
 (); 
  
 for 
  
 ( 
 const 
  
 eventRow 
  
 of 
  
 eventRows 
 ) 
  
 { 
  
 const 
  
 event 
  
 = 
  
 DataManagerEvent 
 . 
 create 
 (); 
  
 try 
  
 { 
  
 const 
  
 date 
  
 = 
  
 new 
  
 Date 
 ( 
 eventRow 
 . 
 timestamp 
 ); 
  
 event 
 . 
 eventTimestamp 
  
 = 
  
 { 
  
 seconds 
 : 
  
 Math 
 . 
 floor 
 ( 
 date 
 . 
 getTime 
 () 
  
 / 
  
 1000 
 ), 
  
 nanos 
 : 
  
 ( 
 date 
 . 
 getTime 
 () 
  
 % 
  
 1000 
 ) 
  
 * 
  
 1e6 
 , 
  
 }; 
  
 } 
  
 catch 
  
 ( 
 e 
 ) 
  
 { 
  
 console 
 . 
 warn 
 ( 
  
 `Invalid timestamp format: 
 ${ 
 eventRow 
 . 
 timestamp 
 } 
 . Skipping row.` 
 , 
  
 ); 
  
 continue 
 ; 
  
 } 
  
 if 
  
 ( 
 ! 
 eventRow 
 . 
 transactionId 
 ) 
  
 { 
  
 console 
 . 
 warn 
 ( 
 'Skipping event with no transaction ID' 
 ); 
  
 continue 
 ; 
  
 } 
  
 event 
 . 
 transactionId 
  
 = 
  
 eventRow 
 . 
 transactionId 
 ; 
  
 if 
  
 ( 
 eventRow 
 . 
 eventSource 
 ) 
  
 { 
  
 const 
  
 eventSourceEnumValue 
 : 
  
 number 
  
 | 
  
 undefined 
  
 = 
  
 EventSource 
 [ 
 eventRow 
 . 
 eventSource 
  
 as 
  
 keyof 
  
 typeof 
  
 EventSource 
 ]; 
  
 if 
  
 ( 
 eventSourceEnumValue 
  
 === 
  
 undefined 
 ) 
  
 { 
  
 console 
 . 
 warn 
 ( 
  
 `Skipping event with invalid event_source: 
 ${ 
 eventRow 
 . 
 eventSource 
 } 
 ` 
 , 
  
 ); 
  
 continue 
 ; 
  
 } 
  
 event 
 . 
 eventSource 
  
 = 
  
 eventSourceEnumValue 
 ; 
  
 } 
  
 if 
  
 ( 
 eventRow 
 . 
 gclid 
 ) 
  
 { 
  
 event 
 . 
 adIdentifiers 
  
 = 
  
 { 
 gclid 
 : 
  
 eventRow 
 . 
 gclid 
 }; 
  
 } 
  
 if 
  
 ( 
 eventRow 
 . 
 currency 
 ) 
  
 { 
  
 event 
 . 
 currency 
  
 = 
  
 eventRow 
 . 
 currency 
 ; 
  
 } 
  
 if 
  
 ( 
 eventRow 
 . 
 value 
 ) 
  
 { 
  
 event 
 . 
 conversionValue 
  
 = 
  
 eventRow 
 . 
 value 
 ; 
  
 } 
  
 const 
  
 userData 
  
 = 
  
 UserData 
 . 
 create 
 (); 
  
 // Adds a UserIdentifier for each valid email address for the eventRecord. 
  
 if 
  
 ( 
 eventRow 
 . 
 emails 
 ) 
  
 { 
  
 for 
  
 ( 
 const 
  
 email 
  
 of 
  
 eventRow 
 . 
 emails 
 ) 
  
 { 
  
 try 
  
 { 
  
 const 
  
 processedEmail 
  
 = 
  
 formatter 
 . 
 processEmailAddress 
 ( 
  
 email 
 , 
  
 Encoding 
 . 
 HEX 
 , 
  
 ); 
  
 userData 
 . 
 userIdentifiers 
 . 
 push 
 ( 
  
 UserIdentifier 
 . 
 create 
 ({ 
 emailAddress 
 : 
  
 processedEmail 
 }), 
  
 ); 
  
 } 
  
 catch 
  
 ( 
 e 
 ) 
  
 { 
  
 console 
 . 
 warn 
 ( 
 `Invalid email address: 
 ${ 
 email 
 } 
 . Skipping.` 
 ); 
  
 } 
  
 } 
  
 } 
  
 // Adds a UserIdentifier for each valid phone number for the eventRecord. 
  
 if 
  
 ( 
 eventRow 
 . 
 phoneNumbers 
 ) 
  
 { 
  
 for 
  
 ( 
 const 
  
 phoneNumber 
  
 of 
  
 eventRow 
 . 
 phoneNumbers 
 ) 
  
 { 
  
 try 
  
 { 
  
 const 
  
 processedPhone 
  
 = 
  
 formatter 
 . 
 processPhoneNumber 
 ( 
  
 phoneNumber 
 , 
  
 Encoding 
 . 
 HEX 
 , 
  
 ); 
  
 userData 
 . 
 userIdentifiers 
 . 
 push 
 ( 
  
 UserIdentifier 
 . 
 create 
 ({ 
 phoneNumber 
 : 
  
 processedPhone 
 }), 
  
 ); 
  
 } 
  
 catch 
  
 ( 
 e 
 ) 
  
 { 
  
 console 
 . 
 warn 
 ( 
 `Invalid phone: 
 ${ 
 phoneNumber 
 } 
 . Skipping.` 
 ); 
  
 } 
  
 } 
  
 } 
  
 if 
  
 ( 
 userData 
 . 
 userIdentifiers 
 . 
 length 
 > 
 0 
 ) 
  
 { 
  
 event 
 . 
 userData 
  
 = 
  
 userData 
 ; 
  
 } 
  
 events 
 . 
 push 
 ( 
 event 
 ); 
  
 } 
  
 // Sets up the Destination. 
  
 const 
  
 operatingAccountType 
  
 = 
  
 convertToAccountType 
 ( 
  
 argv 
 . 
 operating_account_type 
 , 
  
 'operating_account_type' 
 , 
  
 ); 
  
 const 
  
 destination 
  
 = 
  
 Destination 
 . 
 create 
 ({ 
  
 operatingAccount 
 : 
  
 ProductAccount 
 . 
 create 
 ({ 
  
 accountType 
 : 
  
 operatingAccountType 
 , 
  
 accountId 
 : 
  
 argv 
 . 
 operating_account_id 
 , 
  
 }), 
  
 productDestinationId 
 : 
  
 argv 
 . 
 conversion_action_id 
 , 
  
 }); 
  
 // The login account is optional. 
  
 if 
  
 ( 
 argv 
 . 
 login_account_type 
 ) 
  
 { 
  
 const 
  
 loginAccountType 
  
 = 
  
 convertToAccountType 
 ( 
  
 argv 
 . 
 login_account_type 
 , 
  
 'login_account_type' 
 , 
  
 ); 
  
 destination 
 . 
 loginAccount 
  
 = 
  
 ProductAccount 
 . 
 create 
 ({ 
  
 accountType 
 : 
  
 loginAccountType 
 , 
  
 accountId 
 : 
  
 argv 
 . 
 login_account_id 
 , 
  
 }); 
  
 } 
  
 // The linked account is optional. 
  
 if 
  
 ( 
 argv 
 . 
 linked_account_type 
 ) 
  
 { 
  
 const 
  
 linkedAccountType 
  
 = 
  
 convertToAccountType 
 ( 
  
 argv 
 . 
 linked_account_type 
 , 
  
 'linked_account_type' 
 , 
  
 ); 
  
 destination 
 . 
 linkedAccount 
  
 = 
  
 ProductAccount 
 . 
 create 
 ({ 
  
 accountType 
 : 
  
 linkedAccountType 
 , 
  
 accountId 
 : 
  
 argv 
 . 
 linked_account_id 
 , 
  
 }); 
  
 } 
  
 const 
  
 client 
  
 = 
  
 new 
  
 IngestionServiceClient 
 (); 
  
 let 
  
 requestCount 
  
 = 
  
 0 
 ; 
  
 // Batches requests to send up to the maximum number of events per request. 
  
 for 
  
 ( 
 let 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 events 
 . 
 length 
 ; 
  
 i 
  
 += 
  
 MAX_EVENTS_PER_REQUEST 
 ) 
  
 { 
  
 requestCount 
 ++ 
 ; 
  
 const 
  
 eventsBatch 
  
 = 
  
 events 
 . 
 slice 
 ( 
 i 
 , 
  
 i 
  
 + 
  
 MAX_EVENTS_PER_REQUEST 
 ); 
  
 // Builds the request. 
  
 const 
  
 request 
  
 = 
  
 IngestEventsRequest 
 . 
 create 
 ({ 
  
 destinations 
 : 
  
 [ 
 destination 
 ], 
  
 // Adds events from the current batch. 
  
 events 
 : 
  
 eventsBatch 
 , 
  
 consent 
 : 
  
 Consent 
 . 
 create 
 ({ 
  
 adUserData 
 : 
  
 ConsentStatus 
 . 
 CONSENT_GRANTED 
 , 
  
 adPersonalization 
 : 
  
 ConsentStatus 
 . 
 CONSENT_GRANTED 
 , 
  
 }), 
  
 // Sets encoding to match the encoding used. 
  
 encoding 
 : 
  
 DataManagerEncoding 
 . 
 HEX 
 , 
  
 // Sets validate_only. If true, then the Data Manager API only validates the request 
  
 validateOnly 
 : 
  
 argv 
 . 
 validate_only 
 , 
  
 }); 
  
 const 
  
 [ 
 response 
 ] 
  
 = 
  
 await 
  
 client 
 . 
 ingestEvents 
 ( 
 request 
 ); 
  
 console 
 . 
 log 
 ( 
 `Response for request # 
 ${ 
 requestCount 
 } 
 :\n` 
 , 
  
 response 
 ); 
  
 } 
  
 console 
 . 
 log 
 ( 
 `# of requests sent: 
 ${ 
 requestCount 
 } 
 ` 
 ); 
 } 
 /** 
 * Reads the event data from the given JSON file. 
 * @param {string} jsonFile The path to the JSON file. 
 * @return {EventRow[]} An array of event data. 
 */ 
 function 
  
 readEventDataFile 
 ( 
 jsonFile 
 : 
  
 string 
 ) 
 : 
  
 EventRow 
 [] 
  
 { 
  
 const 
  
 fileContent 
  
 = 
  
 fs 
 . 
 readFileSync 
 ( 
 jsonFile 
 , 
  
 'utf8' 
 ); 
  
 return 
  
 JSON 
 . 
 parse 
 ( 
 fileContent 
 ); 
 } 
 /** 
 * Validates that a given string is an enum value for the AccountType enum, and 
 * if validation passes, returns the AccountType enum value. 
 * @param proposedValue the name of an AccountType enum value 
 * @param paramName the name of the parameter to use in the error message if validation fails 
 * @returns {protos.google.ads.datamanager.v1.ProductAccount.AccountType} The corresponding enum value. 
 * @throws {Error} If the string is not an AccountType enum value. 
 */ 
 function 
  
 convertToAccountType 
 ( 
  
 proposedValue 
 : 
  
 string 
 , 
  
 paramName 
 : 
  
 string 
 , 
 ) 
 : 
  
 protos 
 . 
 google 
 . 
 ads 
 . 
 datamanager 
 . 
 v1 
 . 
 ProductAccount 
 . 
 AccountType 
  
 { 
  
 const 
  
 AccountType 
  
 = 
  
 ProductAccount 
 . 
 AccountType 
 ; 
  
 const 
  
 accountTypeEnumNames 
  
 = 
  
 Object 
 . 
 keys 
 ( 
 AccountType 
 ). 
 filter 
 ( 
 key 
  
 = 
>  
 isNaN 
 ( 
 Number 
 ( 
 key 
 )), 
  
 ); 
  
 if 
  
 ( 
 ! 
 accountTypeEnumNames 
 . 
 includes 
 ( 
 proposedValue 
 )) 
  
 { 
  
 throw 
  
 new 
  
 Error 
 ( 
 `Invalid 
 ${ 
 paramName 
 } 
 : 
 ${ 
 proposedValue 
 } 
 ` 
 ); 
  
 } 
  
 return 
  
 AccountType 
 [ 
 proposedValue 
  
 as 
  
 keyof 
  
 typeof 
  
 AccountType 
 ]; 
 } 
 if 
  
 ( 
 require 
 . 
 main 
  
 === 
  
 module 
 ) 
  
 { 
  
 main 
 (). 
 catch 
 ( 
 console 
 . 
 error 
 ); 
 } 
  

PHP

< ?php 
 // Copyright 2025 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. 
 /** 
 * Sample of sending an IngestEventsRequest without encryption. 
 */ 
 require_once dirname(__DIR__, 1) . '/vendor/autoload.php'; 
 use Google\Ads\DataManager\V1\AdIdentifiers; 
 use Google\Ads\DataManager\V1\Client\IngestionServiceClient; 
 use Google\Ads\DataManager\V1\Consent; 
 use Google\Ads\DataManager\V1\ConsentStatus; 
 use Google\Ads\DataManager\V1\Destination; 
 use Google\Ads\DataManager\V1\Encoding as DataManagerEncoding; 
 use Google\Ads\DataManager\V1\Event; 
 use Google\Ads\DataManager\V1\EventSource; 
 use Google\Ads\DataManager\V1\IngestEventsRequest; 
 use Google\Ads\DataManager\V1\ProductAccount; 
 use Google\Ads\DataManager\V1\ProductAccount\AccountType; 
 use Google\Ads\DataManager\V1\UserData; 
 use Google\Ads\DataManager\V1\UserIdentifier; 
 use Google\Ads\DataManagerUtil\Encoding; 
 use Google\Ads\DataManagerUtil\Formatter; 
 use Google\ApiCore\ApiException; 
 use Google\Protobuf\Timestamp; 
 // The maximum number of events allowed per request. 
 const MAX_EVENTS_PER_REQUEST = 2000; 
 /** 
 * Reads the JSON-formatted event data file. 
 * 
 * @param string $jsonFile The event data file. 
 * @return array A list of associative arrays, each representing an event. 
 */ 
 function readEventDataFile(string $jsonFile): array 
 { 
 $jsonContent = file_get_contents($jsonFile); 
 if ($jsonContent === false) { 
 throw new \RuntimeException(sprintf('Could not read JSON file: %s', $jsonFile)); 
 } 
 $events = json_decode($jsonContent, true); 
 if (json_last_error() !== JSON_ERROR_NONE) { 
 throw new \RuntimeException(sprintf('Invalid JSON in file: %s', $jsonFile)); 
 } 
 return $events; 
 } 
 /** 
 * Runs the sample. 
 * 
 * @param int $operatingAccountType The account type of the operating account. 
 * @param string $operatingAccountId The ID of the operating account. 
 * @param string $conversionActionId The ID of the conversion action. 
 * @param string $jsonFile The JSON file containing event data. 
 * @param bool $validateOnly Whether to enable validateOnly on the request. 
 * @param int|null $loginAccountType The account type of the login account. 
 * @param string|null $loginAccountId The ID of the login account. 
 * @param int|null $linkedAccountType The account type of the linked account. 
 * @param string|null $linkedAccountId The ID of the linked account. 
 */ 
 function main( 
 int $operatingAccountType, 
 string $operatingAccountId, 
 string $conversionActionId, 
 string $jsonFile, 
 bool $validateOnly, 
 ?int $loginAccountType = null, 
 ?string $loginAccountId = null, 
 ?int $linkedAccountType = null, 
 ?string $linkedAccountId = null 
 ): void { 
 // Reads event data from the data file. 
 $eventRecords = readEventDataFile($jsonFile); 
 // Gets an instance of the UserDataFormatter for normalizing and formatting the data. 
 $formatter = new Formatter(); 
 // Builds the events collection for the request. 
 $events = []; 
 foreach ($eventRecords as $eventRecord) { 
 $event = new Event(); 
 if (empty($eventRecord['timestamp'])) { 
 error_log('Skipping event with no timestamp.'); 
 continue; 
 } 
 try { 
 $dateTime = new DateTime($eventRecord['timestamp']); 
 $timestamp = new Timestamp(); 
 $timestamp->fromDateTime($dateTime); 
 $event->setEventTimestamp($timestamp); 
 } catch (\Exception $e) { 
 error_log(sprintf('Skipping event with invalid timestamp: %s', $eventRecord['timestamp'])); 
 continue; 
 } 
 if (empty($eventRecord['transactionId'])) { 
 error_log('Skipping event with no transaction ID'); 
 continue; 
 } 
 $event->setTransactionId($eventRecord['transactionId']); 
 if (!empty($eventRecord['eventSource'])) { 
 try { 
 $event->setEventSource(EventSource::value($eventRecord['eventSource'])); 
 } catch (\UnexpectedValueException $e) { 
 error_log('Skipping event with invalid event source: ' . $eventRecord['eventSource']); 
 continue; 
 } 
 } 
 if (!empty($eventRecord['gclid'])) { 
 $event->setAdIdentifiers((new AdIdentifiers())->setGclid($eventRecord['gclid'])); 
 } 
 if (!empty($eventRecord['currency'])) { 
 $event->setCurrency($eventRecord['currency']); 
 } 
 if (isset($eventRecord['value'])) { 
 $event->setConversionValue($eventRecord['value']); 
 } 
 $userData = new UserData(); 
 $identifiers = []; 
 if (!empty($eventRecord['emails'])) { 
 foreach ($eventRecord['emails'] as $email) { 
 try { 
 $preparedEmail = $formatter->processEmailAddress($email, Encoding::Hex); 
 $identifiers[] = (new UserIdentifier())->setEmailAddress($preparedEmail); 
 } catch (\InvalidArgumentException $e) { 
 // Skips invalid input. 
 error_log(sprintf('Skipping invalid email: %s', $e->getMessage())); 
 continue; 
 } 
 } 
 } 
 if (!empty($eventRecord['phoneNumbers'])) { 
 foreach ($eventRecord['phoneNumbers'] as $phoneNumber) { 
 try { 
 $preparedPhoneNumber = $formatter->processPhoneNumber($phoneNumber, Encoding::Hex); 
 $identifiers[] = (new UserIdentifier())->setPhoneNumber($preparedPhoneNumber); 
 } catch (\InvalidArgumentException $e) { 
 // Skips invalid input. 
 error_log(sprintf('Skipping invalid phone number: %s', $e->getMessage())); 
 continue; 
 } 
 } 
 } 
 if (!empty($identifiers)) { 
 $userData->setUserIdentifiers($identifiers); 
 $event->setUserData($userData); 
 } 
 $events[] = $event; 
 } 
 // Builds the destination for the request. 
 $destination = (new Destination()) 
 ->setOperatingAccount((new ProductAccount()) 
 ->setAccountType($operatingAccountType) 
 ->setAccountId($operatingAccountId)) 
 ->setProductDestinationId($conversionActionId); 
 if ($loginAccountType !== null && $loginAccountId !== null) { 
 $destination->setLoginAccount((new ProductAccount()) 
 ->setAccountType($loginAccountType) 
 ->setAccountId($loginAccountId)); 
 } 
 if ($linkedAccountType !== null && $linkedAccountId !== null) { 
 $destination->setLinkedAccount((new ProductAccount()) 
 ->setAccountType($linkedAccountType) 
 ->setAccountId($linkedAccountId)); 
 } 
 $client = new IngestionServiceClient(); 
 try { 
 $requestCount = 0; 
 // Batches requests to send up to the maximum number of events per request. 
 foreach (array_chunk($events, MAX_EVENTS_PER_REQUEST) as $eventsBatch) { 
 $requestCount++; 
 // Builds the request. 
 $request = (new IngestEventsRequest()) 
 ->setDestinations([$destination]) 
 ->setEvents($eventsBatch) 
 ->setConsent((new Consent()) 
 ->setAdUserData(ConsentStatus::CONSENT_GRANTED) 
 ->setAdPersonalization(ConsentStatus::CONSENT_GRANTED) 
 ) 
 ->setValidateOnly($validateOnly) 
 ->setEncoding(DataManagerEncoding::HEX); 
 echo "Request:\n" . json_encode(json_decode($request->serializeToJsonString()), JSON_PRETTY_PRINT) . "\n"; 
 $response = $client->ingestEvents($request); 
 echo "Response for request #{$requestCount}:\n" . json_encode(json_decode($response->serializeToJsonString()), JSON_PRETTY_PRINT) . "\n"; 
 } 
 echo "# of requests sent: {$requestCount}\n"; 
 } catch (ApiException $e) { 
 echo 'Error sending request: ' . $e->getMessage() . "\n"; 
 } finally { 
 $client->close(); 
 } 
 } 
 // Command-line argument parsing 
 $options = getopt( 
 '', 
 [ 
 'operating_account_type:', 
 'operating_account_id:', 
 'login_account_type::', 
 'login_account_id::', 
 'linked_account_type::', 
 'linked_account_id::', 
 'conversion_action_id:', 
 'json_file:', 
 'validate_only::' 
 ] 
 ); 
 $operatingAccountType = $options['operating_account_type'] ?? null; 
 $operatingAccountId = $options['operating_account_id'] ?? null; 
 $conversionActionId = $options['conversion_action_id'] ?? null; 
 $jsonFile = $options['json_file'] ?? null; 
 // Only validates requests by default. 
 $validateOnly = true; 
 if (array_key_exists('validate_only', $options)) { 
 $value = $options['validate_only']; 
 // `getopt` with `::` returns boolean `false` if the option is passed without a value. 
 if ($value === false || !in_array($value, ['true', 'false'], true)) { 
 echo "Error: --validate_only requires a value of 'true' or 'false'.\n"; 
 exit(1); 
 } 
 $validateOnly = ($value === 'true'); 
 } 
 if (empty($operatingAccountType) || empty($operatingAccountId) || empty($conversionActionId) || empty($jsonFile)) { 
 echo 'Usage: php ingest_events.php ' . 
 '--operating_account_type=<account_type> ' . 
 '--operating_account_id=<account_id> ' . 
 '--conversion_action_id=<conversion_action_id> ' . 
 "--json_file=<path_to_json>\n" . 
 'Optional: --login_account_type=<account_type> --login_account_id=<account_id> ' . 
 '--linked_account_type=<account_type> --linked_account_id=<account_id> ' . 
 "--validate_only=<true|false>\n"; 
 exit(1); 
 } 
 // Converts the operating account type string to an AccountType enum. 
 $parsedOperatingAccountType = AccountType::value($operatingAccountType); 
 if (isset($options['login_account_type']) != isset($options['login_account_id'])) { 
 throw new \InvalidArgumentException( 
 'Must specify either both or neither of login account type and login account ID' 
 ); 
 } 
 $parsedLoginAccountType = null; 
 if (isset($options['login_account_type'])) { 
 // Converts the login account type string to an AccountType enum. 
 $parsedLoginAccountType = AccountType::value($options['login_account_type']); 
 } 
 if (isset($options['linked_account_type']) != isset($options['linked_account_id'])) { 
 throw new \InvalidArgumentException( 
 'Must specify either both or neither of linked account type and linked account ID' 
 ); 
 } 
 $parsedLinkedAccountType = null; 
 if (isset($options['linked_account_type'])) { 
 // Converts the linked account type string to an AccountType enum. 
 $parsedLinkedAccountType = AccountType::value($options['linked_account_type']); 
 } 
 main( 
 $parsedOperatingAccountType, 
 $operatingAccountId, 
 $conversionActionId, 
 $jsonFile, 
 $validateOnly, 
 $parsedLoginAccountType, 
 $options['login_account_id'] ?? null, 
 $parsedLinkedAccountType, 
 $options['linked_account_id'] ?? null 
 ); 
  

Python

 #!/usr/bin/env python 
 # Copyright 2025 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. 
 """Sample of sending an IngestEventsRequest without encryption.""" 
 import 
  
 argparse 
 import 
  
 json 
 import 
  
 logging 
 from 
  
 typing 
  
 import 
 Any 
 , 
 Dict 
 , 
 List 
 , 
 Optional 
 from 
  
 google.ads 
  
 import 
 datamanager_v1 
 from 
  
 google.ads.datamanager_util 
  
 import 
 Formatter 
 from 
  
 google.ads.datamanager_util.format 
  
 import 
 Encoding 
 from 
  
 google.protobuf.timestamp_pb2 
  
 import 
 Timestamp 
 _logger 
 = 
 logging 
 . 
 getLogger 
 ( 
 __name__ 
 ) 
 # The maximum number of events allowed per request. 
 _MAX_EVENTS_PER_REQUEST 
 = 
 10_000 
 def 
  
 main 
 ( 
 operating_account_type 
 : 
 datamanager_v1 
 . 
 ProductAccount 
 . 
 AccountType 
 , 
 operating_account_id 
 : 
 str 
 , 
 conversion_action_id 
 : 
 str 
 , 
 json_file 
 : 
 str 
 , 
 validate_only 
 : 
 bool 
 , 
 login_account_type 
 : 
 Optional 
 [ 
 datamanager_v1 
 . 
 ProductAccount 
 . 
 AccountType 
 ] 
 = 
 None 
 , 
 login_account_id 
 : 
 Optional 
 [ 
 str 
 ] 
 = 
 None 
 , 
 linked_account_type 
 : 
 Optional 
 [ 
 datamanager_v1 
 . 
 ProductAccount 
 . 
 AccountType 
 ] 
 = 
 None 
 , 
 linked_account_id 
 : 
 Optional 
 [ 
 str 
 ] 
 = 
 None 
 , 
 ) 
 - 
> None 
 : 
  
 """Runs the sample. 
 Args: 
 operating_account_type: the account type of the operating account. 
 operating_account_id: the ID of the operating account. 
 json_file: the JSON file containing event data. 
 validate_only: whether to enable validate_only on the request. 
 login_account_type: the account type of the login account. 
 login_account_id: the ID of the login account. 
 linked_account_type: the account type of the linked account. 
 linked_account_id: the ID of the linked account. 
 """ 
 # Gets an instance of the formatter. 
 formatter 
 : 
 Formatter 
 = 
 Formatter 
 () 
 # Reads the input file. 
 event_rows 
 : 
 List 
 [ 
 Dict 
 [ 
 str 
 , 
 Any 
 ]] 
 = 
 read_event_data_file 
 ( 
 json_file 
 ) 
 events 
 : 
 List 
 [ 
 datamanager_v1 
 . 
 Event 
 ] 
 = 
 [] 
 for 
 event_row 
 in 
 event_rows 
 : 
 event 
 = 
 datamanager_v1 
 . 
 Event 
 () 
 try 
 : 
 event_timestamp 
 = 
 Timestamp 
 () 
 event_timestamp 
 . 
 FromJsonString 
 ( 
 str 
 ( 
 event_row 
 [ 
 "timestamp" 
 ])) 
 event 
 . 
 event_timestamp 
 = 
 event_timestamp 
 except 
 ValueError 
 : 
 _logger 
 . 
 warning 
 ( 
 "Invalid timestamp format: 
 %s 
 . Skipping row." 
 , 
 event_row 
 [ 
 "timestamp" 
 ], 
 ) 
 continue 
 if 
 "transactionId" 
 not 
 in 
 event_row 
 : 
 _logger 
 . 
 warning 
 ( 
 "Skipping event with no transaction ID" 
 ) 
 continue 
 event 
 . 
 transaction_id 
 = 
 event_row 
 [ 
 "transactionId" 
 ] 
 if 
 "eventSource" 
 in 
 event_row 
 : 
 event 
 . 
 event_source 
 = 
 event_row 
 [ 
 "eventSource" 
 ] 
 if 
 "gclid" 
 in 
 event_row 
 : 
 event 
 . 
 ad_identifiers 
 = 
 datamanager_v1 
 . 
 AdIdentifiers 
 ( 
 gclid 
 = 
 event_row 
 [ 
 "gclid" 
 ] 
 ) 
 if 
 "currency" 
 in 
 event_row 
 : 
 event 
 . 
 currency 
 = 
 event_row 
 [ 
 "currency" 
 ] 
 if 
 "value" 
 in 
 event_row 
 : 
 event 
 . 
 conversion_value 
 = 
 event_row 
 [ 
 "value" 
 ] 
 user_data 
 = 
 datamanager_v1 
 . 
 UserData 
 () 
 # Adds a UserIdentifier for each valid email address for the event row. 
 if 
 "emails" 
 in 
 event_row 
 : 
 for 
 email 
 in 
 event_row 
 [ 
 "emails" 
 ]: 
 try 
 : 
 processed_email 
 : 
 str 
 = 
 formatter 
 . 
 process_email_address 
 ( 
 email 
 , 
 Encoding 
 . 
 HEX 
 ) 
 user_data 
 . 
 user_identifiers 
 . 
 append 
 ( 
 datamanager_v1 
 . 
 UserIdentifier 
 ( 
 email_address 
 = 
 processed_email 
 ) 
 ) 
 except 
 ValueError 
 : 
 # Skips invalid input. 
 _logger 
 . 
 warning 
 ( 
 "Invalid email address: 
 %s 
 . Skipping." 
 , 
 event_row 
 [ 
 "email_address" 
 ], 
 ) 
 # Adds a UserIdentifier for each valid phone number for the event row. 
 if 
 "phoneNumbers" 
 in 
 event_row 
 : 
 for 
 phone_number 
 in 
 event_row 
 [ 
 "phoneNumbers" 
 ]: 
 try 
 : 
 processed_phone 
 : 
 str 
 = 
 formatter 
 . 
 process_phone_number 
 ( 
 phone_number 
 , 
 Encoding 
 . 
 HEX 
 ) 
 user_data 
 . 
 user_identifiers 
 . 
 append 
 ( 
 datamanager_v1 
 . 
 UserIdentifier 
 ( 
 phone_number 
 = 
 processed_phone 
 ) 
 ) 
 except 
 ValueError 
 : 
 # Skips invalid input. 
 _logger 
 . 
 warning 
 ( 
 "Invalid phone: 
 %s 
 . Skipping." 
 , 
 event_row 
 [ 
 "phone_number" 
 ], 
 ) 
 if 
 user_data 
 . 
 user_identifiers 
 : 
 event 
 . 
 user_data 
 = 
 user_data 
 # Adds the event to the list of events to send in the request. 
 events 
 . 
 append 
 ( 
 event 
 ) 
 # Configures the destination. 
 destination 
 : 
 datamanager_v1 
 . 
 Destination 
 = 
 datamanager_v1 
 . 
 Destination 
 () 
 destination 
 . 
 operating_account 
 . 
 account_type 
 = 
 operating_account_type 
 destination 
 . 
 operating_account 
 . 
 account_id 
 = 
 operating_account_id 
 destination 
 . 
 product_destination_id 
 = 
 str 
 ( 
 conversion_action_id 
 ) 
 if 
 login_account_type 
 or 
 login_account_id 
 : 
 if 
 bool 
 ( 
 login_account_type 
 ) 
 != 
 bool 
 ( 
 login_account_id 
 ): 
 raise 
 ValueError 
 ( 
 "Must specify either both or neither of login " 
 + 
 "account type and login account ID" 
 ) 
 destination 
 . 
 login_account 
 . 
 account_type 
 = 
 login_account_type 
 destination 
 . 
 login_account 
 . 
 account_id 
 = 
 login_account_id 
 if 
 linked_account_type 
 or 
 linked_account_id 
 : 
 if 
 bool 
 ( 
 linked_account_type 
 ) 
 != 
 bool 
 ( 
 linked_account_id 
 ): 
 raise 
 ValueError 
 ( 
 "Must specify either both or neither of linked account " 
 + 
 "type and linked account ID" 
 ) 
 destination 
 . 
 linked_account 
 . 
 account_type 
 = 
 linked_account_type 
 destination 
 . 
 linked_account 
 . 
 account_id 
 = 
 linked_account_id 
 # Creates a client for the ingestion service. 
 client 
 : 
 datamanager_v1 
 . 
 IngestionServiceClient 
 = 
 ( 
 datamanager_v1 
 . 
 IngestionServiceClient 
 () 
 ) 
 # Batches requests to send up to the maximum number of events per 
 # request. 
 request_count 
 = 
 0 
 for 
 i 
 in 
 range 
 ( 
 0 
 , 
 len 
 ( 
 events 
 ), 
 _MAX_EVENTS_PER_REQUEST 
 ): 
 request_count 
 += 
 1 
 events_batch 
 = 
 events 
 [ 
 i 
 : 
 i 
 + 
 _MAX_EVENTS_PER_REQUEST 
 ] 
 # Sends the request. 
 request 
 : 
 datamanager_v1 
 . 
 IngestEventsRequest 
 = 
 ( 
 datamanager_v1 
 . 
 IngestEventsRequest 
 ( 
 destinations 
 = 
 [ 
 destination 
 ], 
 # Adds events from the current batch. 
 events 
 = 
 events_batch 
 , 
 consent 
 = 
 datamanager_v1 
 . 
 Consent 
 ( 
 ad_user_data 
 = 
 datamanager_v1 
 . 
 ConsentStatus 
 . 
 CONSENT_GRANTED 
 , 
 ad_personalization 
 = 
 datamanager_v1 
 . 
 ConsentStatus 
 . 
 CONSENT_GRANTED 
 , 
 ), 
 # Sets encoding to match the encoding used. 
 encoding 
 = 
 datamanager_v1 
 . 
 Encoding 
 . 
 HEX 
 , 
 # Sets validate_only. If true, then the Data Manager API only 
 # validates the request but doesn't apply changes. 
 validate_only 
 = 
 validate_only 
 , 
 ) 
 ) 
 # Sends the request. 
 response 
 : 
 datamanager_v1 
 . 
 IngestEventsResponse 
 = 
 client 
 . 
 ingest_events 
 ( 
 request 
 = 
 request 
 ) 
 # Logs the response. 
 _logger 
 . 
 info 
 ( 
 "Response for request # 
 %d 
 : 
 \n 
 %s 
 " 
 , 
 request_count 
 , 
 response 
 ) 
 _logger 
 . 
 info 
 ( 
 "# of requests sent: 
 %d 
 " 
 , 
 request_count 
 ) 
 def 
  
 read_event_data_file 
 ( 
 json_file 
 : 
 str 
 ) 
 - 
> List 
 [ 
 Dict 
 [ 
 str 
 , 
 Any 
 ]]: 
  
 """Reads the JSON-formatted event data file. 
 Args: 
 json_file: the event data file. 
 """ 
 with 
 open 
 ( 
 json_file 
 , 
 "r" 
 ) 
 as 
 f 
 : 
 return 
 json 
 . 
 load 
 ( 
 f 
 ) 
 if 
 __name__ 
 == 
 "__main__" 
 : 
 # Configures logging. 
 logging 
 . 
 basicConfig 
 ( 
 level 
 = 
 logging 
 . 
 INFO 
 ) 
 parser 
 = 
 argparse 
 . 
 ArgumentParser 
 ( 
 description 
 = 
 ( 
 "Sends events from a JSON file to a destination." 
 ), 
 fromfile_prefix_chars 
 = 
 "@" 
 , 
 ) 
 # The following argument(s) should be provided to run the example. 
 parser 
 . 
 add_argument 
 ( 
 "--operating_account_type" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 True 
 , 
 help 
 = 
 "The account type of the operating account." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--operating_account_id" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 True 
 , 
 help 
 = 
 "The ID of the operating account." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--conversion_action_id" 
 , 
 type 
 = 
 int 
 , 
 required 
 = 
 True 
 , 
 help 
 = 
 "The ID of the conversion action" 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--login_account_type" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 False 
 , 
 help 
 = 
 "The account type of the login account." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--login_account_id" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 False 
 , 
 help 
 = 
 "The ID of the login account." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--linked_account_type" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 False 
 , 
 help 
 = 
 "The account type of the linked account." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--linked_account_id" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 False 
 , 
 help 
 = 
 "The ID of the linked account." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--json_file" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 True 
 , 
 help 
 = 
 "JSON file containing user data to ingest." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--validate_only" 
 , 
 choices 
 = 
 [ 
 "true" 
 , 
 "false" 
 ], 
 default 
 = 
 "true" 
 , 
 help 
 = 
 """Whether to enable validate_only on the request. Must be 
 'true' or 'false'. Defaults to 'true'.""" 
 , 
 ) 
 args 
 = 
 parser 
 . 
 parse_args 
 () 
 main 
 ( 
 args 
 . 
 operating_account_type 
 , 
 args 
 . 
 operating_account_id 
 , 
 args 
 . 
 conversion_action_id 
 , 
 args 
 . 
 json_file 
 , 
 args 
 . 
 validate_only 
 == 
 "true" 
 , 
 args 
 . 
 login_account_type 
 , 
 args 
 . 
 login_account_id 
 , 
 args 
 . 
 linked_account_type 
 , 
 args 
 . 
 linked_account_id 
 , 
 ) 
  

Success responses

A successful request returns a response with an object containing a requestId .

  { 
  
 "requestId" 
 : 
  
 "126365e1-16d0-4c81-9de9-f362711e250a" 
 } 
 

Record the requestId returned so you can retrieve diagnostics as each destination in the request is processed.

Failure responses

A failed request results in an error response status code such as 400 Bad Request , and a response with error details.

For example, an emailAddress containing a plain text string instead of a hex encoded value produces the following response:

  { 
  
 "error" 
 : 
  
 { 
  
 "code" 
 : 
  
 400 
 , 
  
 "message" 
 : 
  
 "There was a problem with the request." 
 , 
  
 "status" 
 : 
  
 "INVALID_ARGUMENT" 
 , 
  
 "details" 
 : 
  
 [ 
  
 { 
  
 "@type" 
 : 
  
 "type.googleapis.com/google.rpc.ErrorInfo" 
 , 
  
 "reason" 
 : 
  
 "INVALID_ARGUMENT" 
 , 
  
 "domain" 
 : 
  
 "datamanager.googleapis.com" 
  
 }, 
  
 { 
  
 "@type" 
 : 
  
 "type.googleapis.com/google.rpc.BadRequest" 
 , 
  
 "fieldViolations" 
 : 
  
 [ 
  
 { 
  
 "field" 
 : 
  
 "events.events[0].user_data.user_identifiers" 
 , 
  
 "description" 
 : 
  
 "Email is not hex encoded." 
 , 
  
 "reason" 
 : 
  
 "INVALID_HEX_ENCODING" 
  
 } 
  
 ] 
  
 } 
  
 ] 
  
 } 
 } 
 

An emailAddress that isn't hashed and is only hex encoded produces the following response:

  { 
  
 "error" 
 : 
  
 { 
  
 "code" 
 : 
  
 400 
 , 
  
 "message" 
 : 
  
 "There was a problem with the request." 
 , 
  
 "status" 
 : 
  
 "INVALID_ARGUMENT" 
 , 
  
 "details" 
 : 
  
 [ 
  
 { 
  
 "@type" 
 : 
  
 "type.googleapis.com/google.rpc.ErrorInfo" 
 , 
  
 "reason" 
 : 
  
 "INVALID_ARGUMENT" 
 , 
  
 "domain" 
 : 
  
 "datamanager.googleapis.com" 
  
 }, 
  
 { 
  
 "@type" 
 : 
  
 "type.googleapis.com/google.rpc.BadRequest" 
 , 
  
 "fieldViolations" 
 : 
  
 [ 
  
 { 
  
 "field" 
 : 
  
 "events.events[0]" 
 , 
  
 "reason" 
 : 
  
 "INVALID_SHA256_FORMAT" 
  
 } 
  
 ] 
  
 } 
  
 ] 
  
 } 
 } 
 

Send events for multiple destinations

If your data contains events for different destinations, you can send them in the same request by using destination references. See Limits and quotas for the maximum number of destinations per request.

For example, if you have an event for conversion action ID 123456789 and another event for conversion action ID 777111122 , send both events in a single request by setting the reference of each Destination . The reference is user-defined. The only requirement is that each Destination has a unique reference . Here's the modified destinations list for the request:

   
 "destinations" 
 : 
  
 [ 
  
 { 
  
 "operatingAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " OPERATING_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " OPERATING_ACCOUNT_ID 
" 
  
 }, 
  
 "productDestinationId" 
 : 
  
 " PRODUCT_DESTINATION_ID 
" 
 , 
  
 "reference" 
 : 
  
 "destination_a" 
  
 }, 
  
 { 
  
 "operatingAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " OPERATING_ACCOUNT_2_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " OPERATING_ACCOUNT_2_ID 
" 
  
 }, 
  
 "productDestinationId" 
 : 
  
 "777111122" 
 , 
  
 "reference" 
 : 
  
 "destination_b" 
  
 } 
  
 ] 
 

Set the destinationReferences of each Event to send it to one or more specific destinations. For example, here's an Event that's only for the first Destination , so its destinationReferences list only contains the reference of the first Destination :

  { 
  
 "adIdentifiers" 
 : 
  
 { 
  
 "gclid" 
 : 
  
 "GCLID_1" 
  
 }, 
  
 "conversionValue" 
 : 
  
 1.99 
 , 
  
 "currency" 
 : 
  
 "USD" 
 , 
  
 "eventTimestamp" 
 : 
  
 "2025-06-10T20:07:01Z" 
 , 
  
 "transactionId" 
 : 
  
 "ABC798654321" 
 , 
  
 "eventSource" 
 : 
  
 "WEB" 
 , 
  
 "destinationReferences" 
 : 
  
 [ 
  
 "destination_a" 
  
 ] 
 } 
 

The destinationReferences field is a list, so you can specify multiple destinations for an event. If you don't set the destinationReferences of an Event , the Data Manager API sends the event to all of the destinations in the request.

If an event has multiple destinations, the Data Manager API sends relevant fields to each destination. For example, if an event has a Google Ads destination and a Google Analytics destination, the API includes Google Analytics fields such as clientId or eventName when sending the event to the Google Analytics destination, and includes Google Ads fields such as customVariables when sending the event to the Google Ads destination.

Next steps

Create a Mobile Website
View Site in Mobile | Classic
Share by: