Puoi seguire questa guida rapida per acquisire familiarità con l'invio dei dati sugli eventi.
Utilizza l'API Data Manager per uno dei seguenti scenari:
Invia le conversioni del tag Google Ads o gli eventi
purchasedi Google Analytics come un'origine dati aggiuntiva per le conversioni del tag, per massimizzare gli indicatori di interazione con gli annunci e rafforzare i dati e il rendimento complessivo.Questa funzionalità è disponibile per tutti gli account Google Ads, ma solo per le proprietà Google Analytics in una lista consentita. Compila il modulo se ti interessa aggiungere la tua proprietà Google Analytics alla lista consentita.
Invia i dati sugli eventi per le conversioni offline di Google Ads o le conversioni avanzate per i lead.
Scegli la versione della guida che vuoi visualizzare:
In questa guida rapida, completerai i seguenti passaggi:
- Prepara un
Destinationper ricevere i dati degli eventi. - Prepara i dati sugli eventi da inviare.
- Crea una richiesta
IngestionServiceper gli eventi. - Invia la richiesta con Explorer API di Google.
- Comprendere le risposte di esito positivo e negativo.
Preparare le destinazioni
Prima di poter inviare i dati, devi preparare almeno un Destination per i dati. Ecco un Destination di esempio che puoi utilizzare:
{
"operatingAccount": {
"accountType": "OPERATING_ACCOUNT_TYPE",
"accountId": "OPERATING_ACCOUNT_ID"
},
"loginAccount": {
"accountType": "LOGIN_ACCOUNT_TYPE",
"accountId": "LOGIN_ACCOUNT_ID"
},
"productDestinationId": "PRODUCT_DESTINATION_ID"
}
Di seguito sono riportati i campi di un Destination. Consulta la sezione Configurare le destinazioni per ulteriori dettagli ed esempi di destinazioni per diversi scenari.
operatingAccountL'account che riceve gli eventi.
Per gli eventi inviati come origine dati aggiuntiva, l'account operativo può essere un account Google Ads o una proprietà Google Analytics.
Se
accountTypeèGOOGLE_ANALYTICS_PROPERTY, le credenziali della richiesta devono essere per un utente Google Analytics con il ruolo di editor o amministratore per la proprietà.Per le conversioni offline e le conversioni avanzate per i lead, l'account operativo deve essere un account Google Ads.
loginAccount- L'account in cui l'Account Google per le credenziali è un utente.
productDestinationIdL'ID dell'entità in
operatingAccountche riceve gli eventi.Per gli eventi inviati come origine dati aggiuntiva,
productDestinationIddeve essere uno dei seguenti:L'ID di una conversione Google Ads con
typeimpostato suWEBPAGE. Nell'interfaccia utente di Google Ads, l'origine conversione per un'azione di conversioneWEBPAGEè Sito web.L'ID misurazione di uno stream web di Google Analytics. Non puoi inviare eventi come origine dati aggiuntiva a uno stream di app per iOS o Android di Google Analytics.
Per le conversioni offline o le conversioni avanzate per i lead, il
productDestinationIddeve essere l'ID di un'azione di conversione Google Ads contypeimpostato suUPLOAD_CLICKS. Nell'interfaccia utente di Google Ads, l'origine conversione per un'azione di conversioneUPLOAD_CLICKSè Sito web (importazione dai clic).
L'esempio in questa guida mostra come creare una richiesta che invia ogni evento alla stessa destinazione. Se vuoi inviare eventi per più destinazioni nella stessa richiesta, consulta Inviare eventi per più destinazioni.
Preparare i dati evento
Considera i seguenti dati sugli eventi. Ogni tabella corrisponde a un evento di conversione. Ogni evento di conversione ha un timestamp dell'evento, la relativa azione di conversione e il valore di conversione.
Ogni evento potrebbe avere identificatori pubblicitari, come gclid, o identificatori utente, come
indirizzi email, numeri di telefono e informazioni sull'indirizzo. Un evento può anche
avere:
- Informazioni sull'utente valutate al momento dell'evento, ad esempio il valore del cliente o se è un cliente nuovo, di ritorno o riattivato.
- Dati del carrello.
- Parametri evento o proprietà utente aggiuntivi per una destinazione, ad esempio
client_idouser_idper Google Analytics.
Ecco i dati dell'evento:
Evento 1
| Evento n. 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 |
| Articoli nel carrello | |
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 |
Evento 2
| Evento n. 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 |
|
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 |
| Articoli nel carrello | |
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 |
Formattare i dati
Formatta i campi come specificato nella guida alla formattazione. Ecco i dati dell'evento dopo la formattazione:
Evento 1
| Evento n. 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 |
| Articoli nel carrello | |
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 |
Evento 2
| Evento n. 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 |
|
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 |
| Articoli nel carrello | |
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 |
Esegui l'hashing e la codifica dei dati
Inoltre, gli indirizzi email, i nomi e i cognomi formattati devono essere sottoposti ad hashing utilizzando l'algoritmo SHA-256 e codificati utilizzando la codifica esadecimale o Base64. Ecco i dati evento dopo la formattazione, l'hashing e la codifica utilizzando la codifica esadecimale:
Evento 1
| Evento n. 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 |
| Articoli nel carrello | |
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 |
Evento 2
| Evento n. 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 |
|
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 |
| Articoli nel carrello | |
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 |
Converti i dati in oggetti Event
Converti i dati formattati e sottoposti ad hashing di ogni evento in un Event. Compila i
seguenti campi come indicato:
Imposta
eventTimestampsull'ora in cui si è verificato l'evento.Gli eventi per Google Analytics devono avere un
eventTimestampnelle ultime 72 ore.Imposta i campi obbligatori per il tuo caso d'uso.
Caso d'uso Identificatori transactionIdeventSourceConversioni offline o conversioni avanzate per i lead Required. Imposta almeno uno dei seguenti valori: adIdentifierscon almeno uno dei valorigclid,gbraidowbraidimpostato- Attributi sessione
userData
Facoltativo Required. Imposta uno dei valori enum per EventSource.Eventi inviati come origine dati aggiuntiva a una destinazione Google Ads Required. Imposta almeno uno dei seguenti valori: adIdentifierscon almeno uno dei valorigclid,gbraidowbraidimpostatouserData
Obbligatorio Facoltativo. Se impostato, deve essere WEB.Eventi inviati come origine dati aggiuntiva a una destinazione Google Analytics Required. Imposta almeno uno dei seguenti valori: clientIdadIdentifierscongclidimpostatouserId
Obbligatorio Facoltativo. Se impostato, deve essere WEB.Se invii eventi come origine dati aggiuntiva a una destinazione Google Ads, consulta Come Google gestisce i dati delle origini dati aggiuntive.
Compila gli altri campi per i quali hai un valore per l'evento. Consulta la documentazione di riferimento
Eventper l'elenco completo dei campi disponibili.
In che modo Google gestisce i dati delle origini dati aggiuntive
All'interno della stessa azione di conversione, Google utilizza transactionId per
rimuovere i duplicati degli eventi di conversione inviati da origini diverse (come il tag del sito web
e le richieste di importazione dell'API Data Manager). La seguente tabella spiega come vengono elaborati i dati
delle richieste di importazione.
| Scenario | Campo di dati | Come viene gestito |
|---|---|---|
transactionId CORRISPONDE a un evento tag esistente
|
conversionValue (con currencyCode) |
Aggiornato. Il valore Nota:durante il periodo di prova iniziale di 14 giorni per un'azione di conversione, gli aggiornamenti del valore sono disattivati. Il valore del tag non verrà sostituito nei report di Google Ads fino al termine del periodo di prova. |
transactionId CORRISPONDE a un evento tag esistente |
Altri campi, ad eccezione di conversionValue o
currencyCode (ad esempio, adIdentifiers.gclid).
|
Ignorato. Gli altri valori dei campi dell'origine dati aggiuntiva non sovrascriveranno i valori dei campi registrati originariamente dal tag Google per le transazioni corrispondenti. |
transactionId NON corrisponde ad alcun evento esistente |
Tutti i dati forniti (ad esempio, userData,
conversionValue, currencyCode)
|
Vengono utilizzati per creare un nuovo evento di conversione. Google tenterà quindi di attribuire questa nuova conversione a un clic sull'annuncio utilizzando gli identificatori che hai fornito, ad esempio Nota:durante il periodo di prova iniziale di 14 giorni, queste conversioni appena create verranno visualizzate nei report, ma non verranno utilizzate per le offerte. Al termine del periodo di prova, diventeranno automaticamente disponibili per le offerte. |
Aggiungere attributi sessione
Se invii conversioni offline o conversioni avanzate per i lead, aggiungi attributi di sessione quando non sono disponibili altri identificatori pubblicitari, ad esempio GCLID o WBRAID. Puoi includere anche gli attributi di sessione oltre ad altri identificatori pubblicitari.
Gli attributi sessione forniscono ulteriore contesto e indicatori aggiuntivi sull'interazione dell'utente con il tuo sito web, il che può migliorare la misurazione delle conversioni, la generazione di report e l'accuratezza delle offerte.
Nell'API Data Manager, puoi utilizzare due approcci per inviare gli attributi della sessione:
Consigliato: imposta il campo
sessionAttributesdiadIdentifierssulla stringa degli attributi di sessione con codifica base64. Segui le istruzioni riportate in Come acquisire session_attributes per modificare le pagine di invio del modulo in modo da acquisire la stringa codificata.Se non puoi utilizzare JavaScript, acquisisci i singoli campi degli attributi di sessione e aggiungi ciascuno all'elenco
experimentalFieldscomeExperimentalFieldseparato:gad_campaignidsession_start_time_usecgad_sourcelanding_page_urllanding_page_referrer
Se hai un valore per l'attributo di sessione
landing_page_user_agent, invialo nel campouserAgentdiadIdentifiers.landingPageDeviceInfo.Ecco le best practice per l'invio di singole coppie chiave-valore:
- Invia costantemente
gad_campaignidesession_start_time_usec. Questi campi sono fondamentali per un'attribuzione accurata. - Non fornire un valore
landing_page_urlimpreciso o parziale, ad esempio una stringa segnaposto, un percorso dell'applicazione interno o un URL incompleto. Omettilanding_page_urlse non disponi dell'URL completo e accurato.
Ecco una parte di un evento di esempio con voci in
experimentalFieldspergad_campaignidesession_start_time_usece lo user agent nel campolandingPageDeviceInfo:{ ..., "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" } } }
Aggiungere informazioni su Google Analytics
Se le destinazioni di un evento inviato come origine dati aggiuntiva includono una proprietà Google Analytics, compila i seguenti campi come indicato:
eventNameRequired. Il nome dell'evento Google Analytics.
transactionIdObbligatorio . L'identificatore univoco dell'evento.
- Almeno un identificatore
Deve essere impostato almeno uno dei seguenti campi:
clientId: identificatore univoco per un'istanza utente di un client web. Consulta Inviare un evento a Measurement Protocol.userId: un identificatore univoco per un utente. Per saperne di più, consulta Misurare l'attività sulle varie piattaforme con User-ID.
destinationReferencesObbligatorio se l'elenco
destinationsa livello di richiesta contiene più di unDestinationGoogle Analytics. Aggiungi una voce adestinationReferencesper specificare quale destinazione Google Analytics deve ricevere l'evento. Per saperne di più sui riferimenti alle destinazioni, consulta Inviare eventi a più destinazioni.Se
destinationReferencesnon è impostato o ha più voci che fanno riferimento a destinazioni Google Analytics, l'API Data Manager rifiuta l'evento con l'erroreMULTIPLE_DESTINATIONS_FOR_GOOGLE_ANALYTICS_EVENT.userIdFacoltativo. L'User-ID dell'utente.
additionalEventParametersFacoltativo, ma consigliato. Compila questo elenco con tutti i parametri degli eventi Google Analytics che non vengono acquisiti negli altri campi
Event. I parametri possono includere parametri aggiuntivi consigliati dell'eventopurchaseo altri parametri che vuoi acquisire. Utilizza il nome del parametro Google Analytics perparameterNamediEventParameter.Ad esempio, se hai le imposte associate a una transazione, aggiungi una voce a
additionalEventParametersconparameterNameimpostato sutaxevalueimpostato sul costo delle imposte.Sconsigliamo di aggiungere voci per i parametri evento
transactionId,currencyovaluedi Google Analytics. Compila invece i campitransactionId,currencyeconversionValuediEvent, che hanno la precedenza su qualsiasi voce inadditionalEventParameters.
Aggiungere i dati del carrello per gli eventi di acquisto
Compila il campo cartData di Event con informazioni sugli articoli
acquistati. Per ogni articolo acquistato, aggiungi un oggetto Item all'elenco items
di CartData e compila i seguenti campi come indicato:
itemId- Obbligatorio. Un identificatore univoco per l'elemento.
unitPriceRequired. Il prezzo unitario al netto di tasse, spese di spedizione e sconti a livello di evento (a livello di transazione).
Se l'articolo ha uno sconto a livello di articolo, utilizza il prezzo unitario scontato. Ad esempio, se un articolo ha un prezzo unitario di
27.67e uno sconto unitario di6.66, impostaunitPricesu21.01.quantityRequired. La quantità di unità acquistate per questo articolo specifico.
additionalItemParametersCompila questo elenco con tutti i parametri con ambito elemento che non vengono acquisiti negli altri campi
Item. Utilizza il nome del parametro elemento di Google Analytics per ilparameterNamediItemParameter.Ad esempio, se hai il brand e la categoria di un articolo, aggiungi una voce a
additionalItemParametersdell'articolo conparameterNameimpostato suitem_brandevalueimpostato sul nome del brand e un'altra voce conparameterNameimpostato suitem_categoryevalueimpostato sulla categoria dell'articolo.Non consigliamo di aggiungere voci per i parametri degli elementi
quantity,priceoitem_iddi Google Analytics. Compila invece i campiitemId,unitPriceequantitydiItem, che hanno la precedenza su qualsiasi voce inadditionalItemParameters.
Ecco un Event di esempio per i dati formattati, sottoposti ad hashing e codificati del secondo evento, con dati aggiuntivi per 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"
}
]
}
]
}
}
Crea il corpo della richiesta
Per creare il corpo della richiesta, combina destinations e events, imposta il campo encoding e aggiungi gli altri campi della richiesta che vuoi includere, ad esempio validateOnly e consent.
Gli esempi in questa guida non utilizzano la crittografia, ma puoi seguire le istruzioni riportate in Criptare i dati utente per aggiungere la crittografia al tuo processo.
Invia la richiesta
Ecco i passaggi per provare una richiesta dal browser:
- Seleziona la scheda REST e fai clic su Apri in Explorer API per aprire Explorer API in una nuova scheda o finestra.
- Nel corpo della richiesta in Explorer API, sostituisci ogni stringa che inizia con
REPLACE_WITH, ad esempioREPLACE_WITH_OPERATING_ACCOUNT_TYPE, con il valore pertinente. - Fai clic su Esegui nella parte inferiore della pagina di API Explorer e completa le richieste di autorizzazione per inviare la richiesta.
- Imposta
validateOnlysutrueper convalidare la richiesta senza applicare le modifiche. Quando è tutto pronto per applicare le modifiche, impostavalidateOnlysufalse.
Se hai installato una libreria client, seleziona la scheda del linguaggio di programmazione scelto per visualizzare un esempio di codice completo su come creare e inviare una richiesta.
REST
{ "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 }
.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); } } }
Nodo
#!/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, )
Risposte riuscite
Una richiesta riuscita restituisce una risposta con un oggetto contenente un requestId.
{
"requestId": "126365e1-16d0-4c81-9de9-f362711e250a"
}
Registra il requestId restituito in modo da poter recuperare la diagnostica
man mano che ogni destinazione nella richiesta viene elaborata.
Risposte di errore
Una richiesta non riuscita genera un codice di stato di risposta di errore, ad esempio 400 Bad
Request, e una risposta con i dettagli dell'errore.
Ad esempio, un emailAddress contenente una stringa di testo normale anziché un valore con codifica esadecimale produce la seguente risposta:
{
"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"
}
]
}
]
}
}
Un emailAddress non sottoposto ad hashing e codificato solo in formato esadecimale produce la
seguente risposta:
{
"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"
}
]
}
]
}
}
Inviare eventi per più destinazioni
Se i tuoi dati contengono eventi per destinazioni diverse, puoi inviarli nella stessa richiesta utilizzando i riferimenti alle destinazioni.
Ad esempio, se hai un evento per l'ID azione di conversione 123456789 e
un altro evento per l'ID azione di conversione 777111122, invia entrambi gli eventi in un'unica
richiesta impostando reference di ogni Destination. Il reference è
definito dall'utente. L'unico requisito è che ogni Destination abbia un reference univoco. Ecco l'elenco destinations modificato per la richiesta:
"destinations": [
{
"operatingAccount": {
"accountType": "OPERATING_ACCOUNT_TYPE",
"accountId": "OPERATING_ACCOUNT_ID"
},
"loginAccount": {
"accountType": "LOGIN_ACCOUNT_TYPE",
"accountId": "LOGIN_ACCOUNT_ID"
},
"productDestinationId": "PRODUCT_DESTINATION_ID",
"reference": "destination_a"
},
{
"operatingAccount": {
"accountType": "OPERATING_ACCOUNT_2_TYPE",
"accountId": "OPERATING_ACCOUNT_2_ID"
},
"loginAccount": {
"accountType": "LOGIN_ACCOUNT_2_TYPE",
"accountId": "LOGIN_ACCOUNT_2_ID"
},
"productDestinationId": "777111122",
"reference": "destination_b"
}
]
Imposta il destinationReferences di ogni Event per inviarlo a una o più destinazioni specifiche. Ad esempio, ecco un Event valido solo per il primo
Destination, quindi il suo elenco destinationReferences contiene solo il
reference del primo Destination:
{
"adIdentifiers": {
"gclid": "GCLID_1"
},
"conversionValue": 1.99,
"currency": "USD",
"eventTimestamp": "2025-06-10T20:07:01Z",
"transactionId": "ABC798654321",
"eventSource": "WEB",
"destinationReferences": [
"destination_a"
]
}
Il campo destinationReferences è un elenco, quindi puoi specificare più destinazioni per un evento. Se non imposti destinationReferences di un Event, l'API Data Manager invia l'evento a tutte le destinazioni nella richiesta.
Se un evento ha più destinazioni, l'API Data Manager invia i campi pertinenti a
ogni destinazione. Ad esempio, se un evento ha una destinazione Google Ads e una
destinazione Google Analytics, l'API include campi Google Analytics come clientId
o eventName quando invia l'evento alla destinazione Google Analytics e
include campi Google Ads come customVariables quando invia l'evento alla
destinazione Google Ads.
Passaggi successivi
- Configura l'autenticazione e configura l'ambiente con una libreria client.
- Scopri di più sui requisiti di formattazione, hashing e codifica per ogni tipo di dati.
- Scopri come criptare i dati utente.
- Scopri come recuperare i dati di diagnostica per le tue richieste.
- Scopri di più sulle best practice.
- Scopri di più su limiti e quote.