כדי להכיר את התהליך של שליחת נתוני אירועים, אפשר לעבור על המדריך למתחילים הזה.
אפשר להשתמש ב-Data Manager API באחד מהתרחישים הבאים:
אתם יכולים לשלוח המרות של תג Google Ads או אירועים של
purchaseGoogle Analytics כמקור נתונים נוסף להמרות של התג, כדי למקסם את האותות של האינטראקציה עם המודעות ולשפר את הנתונים והביצועים הכוללים.התכונה הזו זמינה בכל חשבונות Google Ads, אבל היא זמינה רק לנכסי Google Analytics שנכללים ברשימת ההיתרים. אם אתם מעוניינים להוסיף את נכס Google Analytics שלכם לרשימת ההיתרים, מלאו את הטופס.
שליחת נתוני אירועים להמרות אופליין ב-Google Ads או להמרות משופרות לצורך שיוך ללידים.
בוחרים את הגרסה של המדריך שרוצים לראות.
- בוחרים באפשרות מפרסם אם משתמשים בפרטי כניסה לחשבון Google שהוא משתמש בחשבונות המפרסמים שרוצים לנהל.
- בוחרים באפשרות שותף נתונים אם אתם משתמשים בפרטי כניסה לחשבון Google שהוא חשבון משתמש בחשבון של שותף נתונים, ואתם רוצים לנהל חשבונות של מפרסמים שיש להם קישור לשותף לחשבון של שותף הנתונים.
במדריך למתחילים הזה תבצעו את הפעולות הבאות:
- מכינים
Destinationלקבלת נתוני אירועים. - מכינים את נתוני האירועים לשליחה.
- יצירת בקשת
IngestionServiceלאירועים. - שולחים את הבקשה באמצעות Google APIs Explorer.
- הסבר על תגובות הצלחה וכישלון.
הכנת היעדים
לפני ששולחים נתונים, צריך להכין לפחות Destination אחד לנתונים. הנה דוגמה ל-Destination שאפשר להשתמש בה:
{
"operatingAccount": {
"accountType": "OPERATING_ACCOUNT_TYPE",
"accountId": "OPERATING_ACCOUNT_ID"
},
"loginAccount": {
"accountType": "LOGIN_ACCOUNT_TYPE",
"accountId": "LOGIN_ACCOUNT_ID"
},
"productDestinationId": "PRODUCT_DESTINATION_ID"
}
אלה השדות של Destination. במאמר הגדרת יעדים אפשר למצוא פרטים נוספים ודוגמאות ליעדים בתרחישים שונים.
operatingAccountהחשבון שמקבל את האירועים.
במקרה של אירועים שנשלחים כמקור נתונים נוסף, החשבון המפעיל יכול להיות חשבון Google Ads או נכס ב-Google Analytics.
אם הערך של
accountTypeהואGOOGLE_ANALYTICS_PROPERTY, פרטי הכניסה של הבקשה צריכים להיות של משתמש Google Analytics עם תפקיד עורך או אדמין בנכס.במקרה של המרות אופליין והמרות משופרות לצורך שיוך ללידים, חשבון ההפעלה חייב להיות חשבון Google Ads.
loginAccount- החשבון שבו חשבון Google של פרטי הכניסה הוא משתמש.
productDestinationIdהמזהה של הישות ב-
operatingAccountשמקבלת את האירועים.עבור אירועים שנשלחים כמקור נתונים נוסף, הערך של
productDestinationIdחייב להיות אחד מהערכים הבאים:המזהה של המרה ב-Google Ads עם הערך
WEBPAGEשלtype. בממשק המשתמש של Google Ads, מקור ההמרה של פעולת המרה מסוגWEBPAGEהוא אתר.מזהה המדידה של מקור לנתוני אתר ב-Google Analytics. אי אפשר לשלוח אירועים כמקור נתונים נוסף למקור נתוני אפליקציה ל-iOS או לאפליקציית Android ב-Google Analytics.
במקרה של המרות אופליין או המרות משופרות לצורך שיוך ללידים, הערך של
productDestinationIdצריך להיות המזהה של פעולת המרה ב-Google Ads עם הערךUPLOAD_CLICKSשלtype. בממשק המשתמש של Google Ads, מקור ההמרה של פעולת ההמרהUPLOAD_CLICKSהוא אתר (ייבוא מקליקים).
בדוגמה שבמדריך הזה מוסבר איך ליצור בקשה ששולחת כל אירוע לאותו יעד. אם אתם רוצים לשלוח אירועים לכמה יעדים באותה בקשה, כדאי לעיין במאמר בנושא שליחת אירועים לכמה יעדים.
הכנת נתוני אירועים
כדאי לעיין בנתוני האירועים הבאים. כל טבלה תואמת לאירוע המרה אחד. לכל אירוע המרה יש חותמת זמן של האירוע, פעולת ההמרה וערך ההמרה.
יכול להיות שבכל אירוע יהיו מזהי מודעות, כמו gclid, או מזהי משתמשים, כמו כתובות אימייל, מספרי טלפון ופרטי כתובת. אירוע יכול לכלול גם:
- מידע על המשתמש שנבדק בזמן האירוע, כמו הערך של הלקוח או אם הוא לקוח חדש, חוזר או לקוח שחזר להתעניין בעסק.
- נתוני עגלות הקניות.
- פרמטרים נוספים של אירועים או מאפייני משתמש ליעד, כמו
client_idאוuser_idל-Google Analytics.
אלה נתוני האירוע:
אירוע 1
| אירוע מס' 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 |
| פריטים בעגלת הקניות | |
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 |
אירוע 2
| אירוע #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 |
| פריטים בעגלת הקניות | |
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 |
עיצוב הנתונים
מעצבים את השדות בהתאם להנחיות שבמדריך הפורמט. אלה נתוני האירוע אחרי העיצוב:
אירוע 1
| אירוע מס' 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 |
| פריטים בעגלת הקניות | |
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 |
אירוע 2
| אירוע #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 |
| פריטים בעגלת הקניות | |
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 |
גיבוב וקידוד הנתונים
בנוסף, צריך לבצע גיבוב של כתובות האימייל, השמות הפרטיים ושמות המשפחה בפורמט באמצעות אלגוריתם SHA-256 וקידוד באמצעות קידוד הקסדצימלי או Base64. אלה נתוני האירוע אחרי קביעת הפורמט, הגיבוב והקידוד באמצעות קידוד הקסדצימלי:
אירוע 1
| אירוע מס' 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 |
| פריטים בעגלת הקניות | |
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 |
אירוע 2
| אירוע #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 |
| פריטים בעגלת הקניות | |
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 |
המרת הנתונים לאובייקטים של Event
ממירים את הנתונים המפורמטים והמגובבים של כל אירוע ל-Event. ממלאים את השדות הבאים לפי ההנחיות:
מגדירים את
eventTimestampלשעה שבה האירוע התרחש.האירועים ב-Google Analytics צריכים לכלול
eventTimestampב-72 השעות האחרונות.מגדירים את שדות החובה לתרחיש לדוגמה.
תרחיש שימוש מזהים transactionIdeventSourceהמרות אופליין או המרות משופרות לצורך שיוך ללידים נדרש. מגדירים לפחות אחד מהערכים הבאים: adIdentifiersעם לפחות אחד מהערכיםgclid,gbraidאוwbraid- מאפייני סשן
userData
אופציונלי נדרש. מגדירים אחד מערכי ה-enum של EventSource.אירועים שנשלחים כמקור נתונים נוסף ליעד ב-Google Ads נדרש. מגדירים לפחות אחד מהערכים הבאים: -
adIdentifiersעם הגדרה של לפחות אחד מהערכיםgclid,gbraidאוwbraid userData
חובה אופציונלי. אם הוא מוגדר, הערך שלו חייב להיות WEB.אירועים שנשלחים כמקור נתונים נוסף ליעד ב-Google Analytics נדרש. מגדירים לפחות אחד מהערכים הבאים: clientId-
adIdentifiersעם הגדרה שלgclid userId
חובה אופציונלי. אם הוא מוגדר, הערך שלו חייב להיות WEB.אם אתם שולחים אירועים כמקור נתונים נוסף ליעד ב-Google Ads, כדאי לעיין במאמר איך Google מטפלת בנתונים ממקורות נתונים נוספים.
ממלאים את שאר השדות שרוצים להוסיף להם ערך. רשימה מלאה של השדות הזמינים מופיעה במסמכי העזר בנושא
Event.
איך Google מטפלת בנתונים ממקורות נתונים נוספים
במסגרת אותה פעולת המרה, Google משתמשת ב-transactionId כדי לבטל כפילויות של אירועי המרה שנשלחים ממקורות שונים (כמו תג האתר ובקשות להעברה של נתונים באמצעות Data Manager API). בטבלה הבאה מוסבר איך המערכת מעבדת את הנתונים מהבקשות להעברה.
| תרחיש | שדה נתונים | איך המידע מעובד |
|---|---|---|
transactionId זהה לערך של מעקב אירוע קיים באמצעות תג
|
conversionValue (עם currencyCode) |
העדכון הושלם. הערך הערה: במהלך תקופת הניסיון הראשונית של 14 יום לפעולת המרה, האפשרות 'עדכוני ערכים' מושבתת. הערך של התג לא יוחלף בדוחות של Google Ads עד שתקופת הניסיון תסתיים. |
transactionId זהה לערך של מעקב אירוע קיים באמצעות תג |
שדות אחרים חוץ מ-conversionValue או currencyCode (לדוגמה, adIdentifiers.gclid)
|
המערכת מתעלמת מהערך. ערכים אחרים בשדות ממקור הנתונים הנוסף לא יחליפו את הערכים בשדות שתועדו במקור על ידי Google Tag לגבי עסקאות תואמות. |
הערך של transactionId לא תואם לאף אירוע קיים |
כל הנתונים שסופקו (לדוגמה, userData,
conversionValue, currencyCode)
|
המערכת משתמשת בזה ליצירת אירוע המרה חדש. לאחר מכן, Google תנסה לשייך את ההמרה החדשה הזו לקליק על מודעה באמצעות המזהים שסיפקתם (כמו הערה: במהלך תקופת הניסיון הראשונית של 14 יום, ההמרות החדשות שנוצרו יופיעו בדוחות אבל לא ישמשו לבידינג. אחרי שתקופת הניסיון תסתיים, הן ייפתחו לבידינג באופן אוטומטי. |
הוספת מאפייני סשן
אם אתם שולחים המרות אופליין או המרות משופרות לצורך שיוך ללידים, כדאי להוסיף מאפייני סשן כשמזהים אחרים של מודעות, כמו GCLID או WBRAID, לא זמינים. אפשר לכלול גם מאפייני סשן בנוסף למזהי מודעות אחרים.
מאפייני סשן מספקים הקשר ואותות נוספים על האינטראקציה של המשתמשים עם האתר שלכם. האלמנטים האלה יכולים לשפר את הדיוק של מעקב ההמרות, הדיווח והבידינג.
ב-Data Manager API, יש שתי גישות שבהן אפשר להשתמש כדי לשלוח מאפייני סשן:
מומלץ: מגדירים את השדה
sessionAttributesשלadIdentifiersלמחרוזת של מאפייני הסשן בקידוד Base64. כדי לשלוף את המחרוזת המוצפנת, פועלים לפי ההוראות במאמר איך לשלוף מידע מ-session_attributes כדי לשנות את הדפים שבהם מגישים טופס באתר.אם אתם לא יכולים להשתמש ב-JavaScript, אתם יכולים לשלוף את השדות של מאפייני הסשן הנפרדים ולהוסיף כל אחד מהם לרשימה
experimentalFieldsכExperimentalFieldנפרד:gad_campaignidsession_start_time_usecgad_sourcelanding_page_urllanding_page_referrer
אם יש לכם ערך למאפיין הסשן
landing_page_user_agent, אתם צריכים לשלוח אותו בשדהuserAgentשלadIdentifiers.landingPageDeviceInfo.אלה השיטות המומלצות לשליחת צמדי מפתח/ערך נפרדים:
- שליחה עקבית של
gad_campaignidושלsession_start_time_usec. השדות האלה חיוניים לשיוך מדויק. - אל תספקו ערך
landing_page_urlלא מדויק או חלקי, כמו מחרוזת placeholder, נתיב של אפליקציה פנימית או כתובת URL לא מלאה. אם אין לכם את כתובת ה-URL המדויקת והמלאה, אל תציינו את התגlanding_page_url.
קטע מאירוע לדוגמה עם רשומות ב-
experimentalFieldsעבורgad_campaignidו-session_start_time_usec, וסוכן המשתמש בשדהlandingPageDeviceInfo:{ ..., "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" } } }
הוספת מידע מ-Google Analytics
אם היעדים של אירוע שנשלח כמקור נתונים נוסף כוללים נכס ב-Google Analytics, צריך למלא את השדות הבאים כמו שמצוין:
eventNameנדרש. השם של האירוע ב-Google Analytics.
transactionIdחובה . המזהה הייחודי של האירוע.
- מזהה אחד לפחות
צריך להגדיר לפחות אחד מהשדות הבאים:
clientId: מזהה ייחודי של מופע משתמש של לקוח אינטרנט. מידע נוסף זמין במאמר בנושא שליחת אירוע אל Measurement Protocol.
userId: מזהה ייחודי של משתמש. מידע נוסף זמין במאמר בנושא מדידת הפעילות בפלטפורמות שונות באמצעות User-ID.
destinationReferencesחובה אם רשימת
destinationsברמת הבקשה מכילה יותר מ-Google AnalyticsDestinationאחד. מוסיפים רשומה ל-destinationReferencesכדי לציין לאיזה יעד ב-Google Analytics צריך לשלוח את האירוע. מידע נוסף על הפניות ליעדים זמין במאמר בנושא שליחת אירועים למספר יעדים.אם הפרמטר
destinationReferencesלא מוגדר או שיש לו כמה ערכים שמפנים ליעדים ב-Google Analytics, ה-API של המרכז לניהול נתונים דוחה את האירוע עם השגיאהMULTIPLE_DESTINATIONS_FOR_GOOGLE_ANALYTICS_EVENT.userIdאופציונלי. User-ID של המשתמש.
additionalEventParametersאופציונלי, אבל מומלץ. מאכלסים את הרשימה הזו עם פרמטרים של אירועים ב-Google Analytics שלא נכללים בשדות האחרים
Event. הפרמטרים יכולים לכלול פרמטרים מומלצים נוספים מpurchaseהאירוע, או פרמטרים אחרים שרוצים לתעד. משתמשים בשם הפרמטר של Google Analytics עבורparameterNameשלEventParameter.לדוגמה, אם יש לכם את המיסים שמשויכים לעסקה, מוסיפים רשומה ל-
additionalEventParametersעםparameterNameשמוגדר כ-taxועםvalueשמוגדר כעלות המס.אנחנו לא ממליצים להוסיף רשומות לפרמטרים של אירועים ב-Google Analytics:
transactionId,currencyאוvalue. במקום זאת, צריך למלא את הערכיםtransactionId,currencyו-conversionValueשלEvent, שמקבלים עדיפות על פני כל הערכים ב-additionalEventParameters.
הוספת נתוני עגלות קניות לאירועי רכישה
מאכלסים את השדה cartData של Event במידע על הפריטים שנרכשו. לכל פריט שנרכש, מוסיפים אובייקט Item לרשימה items של CartData ומאכלסים את השדות הבאים כמו שמופיע:
itemId- חובה. מזהה ייחודי של הפריט.
unitPriceנדרש. המחיר ליחידה לא כולל מס, משלוח והנחות ברמת האירוע (ברמת העסקה).
אם הפריט נמכר בהנחה ברמת הפריט, צריך להשתמש במחיר היחידה המוזל. לדוגמה, אם המחיר ליחידה של פריט הוא
27.67וההנחה ליחידה היא6.66, צריך להגדיר אתunitPriceכ-21.01.quantityנדרש. כמות היחידות שנרכשו מהפריט הספציפי הזה.
additionalItemParametersמאכלסים את הרשימה הזו בכל הפרמטרים ברמת הפריט שלא נכללים בשדות האחרים של
Item. משתמשים בשם פרמטר הפריט של Google Analytics עבורparameterNameשלItemParameter.לדוגמה, אם יש לכם מותג וקטגוריה של פריט, מוסיפים רשומה ל
additionalItemParametersשל הפריט עםparameterNameשמוגדר כ-item_brandוvalueשמוגדר כשם המותג, ורשומה נוספת עםparameterNameשמוגדר כ-item_categoryוvalueשמוגדר כקטגוריה של הפריט.אנחנו לא ממליצים להוסיף רשומות לפרמטרים של פריטים ב-Google Analytics
quantity,priceאוitem_id. במקום זאת, צריך למלא את הערכיםitemId,unitPriceו-quantityשלItem, שמקבלים עדיפות על פני כל הערכים ב-additionalItemParameters.
הנה דוגמה ל-Event של הנתונים המעוצבים, המגובבים והמקודדים מהאירוע השני, עם נתונים נוספים ל-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"
}
]
}
]
}
}
בניית גוף הבקשה
כדי ליצור את גוף הבקשה, משלבים את destinations ואת events, מגדירים את השדה encoding ומוסיפים שדות בקשה אחרים שרוצים לכלול, כמו validateOnly ו-consent.
הדוגמאות במדריך הזה לא משתמשות בהצפנה, אבל אפשר לפעול לפי ההוראות במאמר הצפנת נתוני משתמשים כדי להוסיף הצפנה לתהליך.
שליחת הבקשה
כדי לנסות לשלוח בקשה מהדפדפן:
- בוחרים בכרטיסייה REST ולוחצים על Open in API Explorer כדי לפתוח את API Explorer בכרטיסייה חדשה או בחלון חדש.
- בגוף הבקשה בכלי API Explorer, מחליפים כל מחרוזת שמתחילה ב-
REPLACE_WITH, כמוREPLACE_WITH_OPERATING_ACCOUNT_TYPE, בערך הרלוונטי. - לוחצים על Execute (ביצוע) בחלק התחתון של הדף API Explorer ומשלימים את הנחיות ההרשאה כדי לשלוח את הבקשה.
- מגדירים את
validateOnlyלערךtrueכדי לאמת את הבקשה בלי להחיל את השינויים. כשמוכנים להחיל את השינויים, מגדירים אתvalidateOnlyלערךfalse.
אם התקנתם ספריית לקוח, תוכלו לבחור את הכרטיסייה של שפת התכנות הרצויה כדי לראות דוגמת קוד מלאה שמראה איך ליצור ולשלוח בקשה.
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); } } }
צומת
#!/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, )
תגובות שבוצעו בהצלחה
אם הבקשה מצליחה, מוחזרת תגובה עם אובייקט שמכיל requestId.
{
"requestId": "126365e1-16d0-4c81-9de9-f362711e250a"
}
כדאי לתעד את הערך requestId שמוחזר כדי שתוכלו לאחזר אבחון כשכל יעד בבקשה יעבור עיבוד.
תגובות שגיאה
בקשה שנכשלה מניבה קוד סטטוס של תגובה עם שגיאה, כמו 400 Bad
Request, ותגובה עם פרטי השגיאה.
לדוגמה, אם emailAddress מכיל מחרוזת של טקסט פשוט במקום ערך מקודד הקסדצימלי, התגובה הבאה תתקבל:
{
"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"
}
]
}
]
}
}
אם מחרוזת emailAddress לא עוברת גיבוב אלא רק קידוד הקסדצימלי, התשובה שמתקבלת היא:
{
"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"
}
]
}
]
}
}
שליחת אירועים למספר יעדים
אם הנתונים שלכם מכילים אירועים ליעדים שונים, אתם יכולים לשלוח אותם באותה בקשה באמצעות הפניות ליעדים.
לדוגמה, אם יש לכם אירוע עם מזהה פעולת ההמרה 123456789 ואירוע נוסף עם מזהה פעולת ההמרה 777111122, אתם יכולים לשלוח את שני האירועים בבקשה אחת על ידי הגדרת reference של כל Destination. הערך של reference מוגדר על ידי המשתמש. הדרישה היחידה היא שלכל Destination יהיה reference ייחודי. זו רשימת destinations ששונתה עבור הבקשה:
"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"
}
]
מגדירים את destinationReferences של כל Event כדי לשלוח אותו ליעד ספציפי אחד או יותר. לדוגמה, הנה Event שרלוונטי רק ל-Destination הראשון, ולכן רשימת ה-destinationReferences שלו מכילה רק את ה-reference של ה-Destination הראשון:
{
"adIdentifiers": {
"gclid": "GCLID_1"
},
"conversionValue": 1.99,
"currency": "USD",
"eventTimestamp": "2025-06-10T20:07:01Z",
"transactionId": "ABC798654321",
"eventSource": "WEB",
"destinationReferences": [
"destination_a"
]
}
השדה destinationReferences הוא רשימה, כך שאפשר לציין כמה יעדים לאירוע. אם לא מגדירים את destinationReferences של Event, ה-API של המרכז לניהול נתונים שולח את האירוע לכל היעדים בבקשה.
אם לאירוע יש כמה יעדים, ה-API של המרכז לניהול נתונים שולח שדות רלוונטיים לכל יעד. לדוגמה, אם לאירוע יש יעד ב-Google Ads ויעד ב-Google Analytics, ה-API כולל שדות של Google Analytics כמו clientId או eventName כששולחים את האירוע ליעד ב-Google Analytics, וכולל שדות של Google Ads כמו customVariables כששולחים את האירוע ליעד ב-Google Ads.
השלבים הבאים
- מגדירים אימות ומכינים את הסביבה באמצעות ספריית לקוח.
- מידע נוסף על הדרישות לגבי פורמט, גיבוב וקידוד של כל סוג נתונים
- איך מצפינים נתוני משתמשים
- איך מאחזרים נתונים מהאבחון של הבקשות.
- שיטות מומלצות
- מידע נוסף על מגבלות ומכסות