يمكنك الاطّلاع على هذا الدليل السريع للتعرّف على كيفية إرسال بيانات الأحداث.
استخدِم Data Manager API في أيّ من السيناريوهات التالية:
أرسِل إحالات ناجحة من علامة تتبُّع "إعلانات Google" أو أحداث
purchaseفي "إحصاءات Google" كمصدر بيانات إضافي للإحالات الناجحة من علامتك، وذلك لزيادة إشارات التفاعل مع الإعلانات إلى أقصى حد وتعزيز بياناتك وأدائك بشكل عام.تتوفّر هذه الميزة لجميع حسابات "إعلانات Google"، ولكنّها لا تتوفّر إلا لمواقع "إحصاءات Google" المدرَجة في قائمة السماح. املأ النموذج إذا كنت مهتمًا بإضافة موقعك على "إحصاءات Google" إلى قائمة السماح.
إرسال بيانات الأحداث إلى الإحالات الناجحة غير الإلكترونية في "إعلانات Google" أو الإحالات الناجحة المحسّنة للعملاء المحتملين
اختَر إصدار الدليل الذي تريد الاطّلاع عليه:
في هذا التشغيل السريع، ستكمل الخطوات التالية:
- جهِّز
Destinationلتلقّي بيانات الأحداث. - إعداد بيانات الأحداث لإرسالها
- أنشئ طلب
IngestionServiceللأحداث. - أرسِل الطلب باستخدام مستكشف واجهات برمجة التطبيقات.
- التعرّف على ردود النجاح والفشل
إعداد الوجهات
قبل إرسال البيانات، عليك إعداد 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" أو موقعًا على "إحصاءات Google".
إذا كانت قيمة
accountTypeهيGOOGLE_ANALYTICS_PROPERTY، يجب أن تكون بيانات اعتماد الطلب خاصة بمستخدم "إحصاءات Google" لديه دور المحرِّر أو المشرف للموقع.بالنسبة إلى الإحالات الناجحة غير الإلكترونية و"الإحالات الناجحة المحسّنة للعملاء المحتملين"، يجب أن يكون الحساب التشغيلي حسابًا على "إعلانات Google".
loginAccount- الحساب الذي يكون فيه حساب Google الخاص ببيانات الاعتماد مستخدمًا
productDestinationIdمعرّف الكيان في
operatingAccountالذي يتلقّى الأحداث.بالنسبة إلى الأحداث المُرسَلة كمصدر بيانات إضافي، يجب أن يكون
productDestinationIdأحد ما يلي:معرّف إحالة ناجحة في "إعلانات Google" تم ضبط
typeعلىWEBPAGE. في واجهة مستخدم "إعلانات Google"، مصدر الإحالة الناجحة لإجراء إحالة ناجحة من النوعWEBPAGEهو الموقع الإلكتروني.رقم تعريف القياس الخاص بمصدر بيانات الموقع الإلكتروني في "إحصاءات Google" لا يمكنك إرسال الأحداث كمصدر بيانات إضافي إلى مصدر بيانات تطبيق iOS أو تطبيق Android في "إحصاءات Google".
بالنسبة إلى الإحالات الناجحة غير الإلكترونية أو الإحالات الناجحة المحسّنة للعملاء المحتملين، يجب أن يكون
productDestinationIdهو معرّف إجراء إحالة ناجحة في "إعلانات Google" مع ضبطtypeعلىUPLOAD_CLICKS. في واجهة مستخدم "إعلانات Google"، يكون مصدر الإحالة الناجحة لإجراء إحالة ناجحة من النوعUPLOAD_CLICKSهو الموقع الإلكتروني (الاستيراد من النقرات).
يوضّح المثال الوارد في هذا الدليل كيفية إنشاء طلب يرسل كل حدث إلى الوجهة نفسها. إذا كنت تريد إرسال أحداث إلى وجهات متعدّدة في الطلب نفسه، اطّلِع على مقالة إرسال أحداث إلى وجهات متعدّدة.
إعداد بيانات الأحداث
ضع في اعتبارك بيانات الحدث التالية. يتطابق كل جدول مع حدث إحالة ناجحة واحد. يتضمّن كل حدث إحالة ناجحة طابعًا زمنيًا للحدث وإجراء الإحالة الناجحة وقيمة الإحالة الناجحة.
قد يتضمّن كل حدث معرّفات إعلانات، مثل gclid، أو معرّفات مستخدمين، مثل عناوين البريد الإلكتروني وأرقام الهواتف ومعلومات العنوان. يمكن أن يتضمّن الحدث أيضًا ما يلي:
- معلومات عن المستخدِم يتم تقييمها في وقت وقوع الحدث، مثل قيمة العميل أو ما إذا كان عميلاً جديدًا أو متكرّرًا أو عميل أعاد التفاعل مع النشاط التجاري.
- بيانات سلة التسوّق
- مَعلمات أحداث أو خصائص مستخدمين إضافية لوجهة، مثل
client_idأوuser_idفي "إحصاءات Google".
في ما يلي بيانات الحدث:
الحدث 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"
eventTimestampخلال آخر 72 ساعة.اضبط الحقول المطلوبة لحالة الاستخدام.
حالة الاستخدام المعرّفات transactionIdeventSourceالإحالات الناجحة غير الإلكترونية أو الإحالات الناجحة المحسّنة للعملاء المحتملين مَعلمة مطلوبة. اضبط أحد الخيارات التالية على الأقل: adIdentifiersمع تحديد قيمة واحدة على الأقل منgclidأوgbraidأوwbraid- سمات الجلسة
userData
اختياري مَعلمة مطلوبة. يجب ضبطها على إحدى قيم التعداد EventSource.الأحداث التي يتم إرسالها كمصدر بيانات إضافي إلى وجهة في "إعلانات Google" مَعلمة مطلوبة. اضبط أحد الخيارات التالية على الأقل: -
adIdentifiersمع ضبط قيمة واحدة على الأقل منgclidأوgbraidأوwbraid userData
مطلوب اختياريّ. في حال ضبطها، يجب أن تكون القيمة WEB.الأحداث المُرسَلة كمصدر بيانات إضافي إلى وجهة في "إحصاءات Google" مَعلمة مطلوبة. اضبط أحد الخيارات التالية على الأقل: clientIdadIdentifiersمع ضبطgcliduserId
مطلوب اختياريّ. في حال ضبطها، يجب أن تكون القيمة WEB.إذا كنت ترسِل أحداثًا كمصدر بيانات إضافي إلى وجهة في "إعلانات Google"، راجِع مقالة طريقة تعامل Google مع بيانات مصدر البيانات الإضافي.
املأ أي حقول أخرى تتضمّن قيمة للحدث. راجِع مستندات
Eventالمرجعية للاطّلاع على القائمة الكاملة للحقول المتاحة.
طريقة معالجة Google لبيانات مصدر البيانات الإضافي
ضِمن إجراء الإحالة الناجحة نفسه، تستخدِم Google transactionId لإزالة تكرار أحداث الإحالات الناجحة التي يتم إرسالها من مصادر مختلفة (مثل علامة موقعك الإلكتروني وطلبات الاستيعاب في Data Manager API). يوضّح الجدول التالي كيفية معالجة البيانات من طلبات الاستيعاب.
| السيناريو | حقل البيانات | طريقة التعامل معه |
|---|---|---|
transactionId يتطابق مع إجراء مسجّل من علامة تتبُّع حالي
|
conversionValue (باستخدام currencyCode) |
تم التعديل. تؤدّي السمة ملاحظة: خلال الفترة التجريبية الأوّلية التي تبلغ 14 يومًا لإجراء إحالة ناجحة، تكون ميزة "تعديلات القيم" غير مفعّلة. لن يتم تجاهل قيمة العلامة في تقارير "إعلانات Google" إلى أن تنتهي الفترة التجريبية. |
transactionId يتطابق مع إجراء مسجّل من علامة تتبُّع حالي |
الحقول الأخرى باستثناء conversionValue أو currencyCode (مثل adIdentifiers.gclid)
|
تم تجاهله. لن تؤدّي قيم الحقول الأخرى الواردة من مصدر البيانات الإضافي إلى استبدال قيم الحقول التي سجّلتها في الأصل علامة التتبُّع من Google للمعاملات المتطابقة. |
لا يتطابق transactionId مع أيّ حدث حالي |
جميع البيانات المقدَّمة (مثل userData وconversionValue وcurrencyCode)
|
تُستخدَم لإنشاء حدث إحالة ناجحة جديد. ستحاول Google بعد ذلك تحديد مصدر هذه الإحالة الناجحة الجديدة على أنّه نقرة على إعلان باستخدام المعرّفات التي قدّمتها (مثل ملاحظة: خلال فترة التجربة الأوّلية التي تبلغ 14 يومًا، ستظهر هذه الإحالات الناجحة التي تم إنشاؤها حديثًا في تقاريرك، ولكن لن يتم استخدامها في ضبط عروض الأسعار. وبعد انتهاء الفترة التجريبية، ستصبح هذه الإحالات الناجحة قابلة لتقديم عروض الأسعار تلقائيًا. |
إضافة سمات الجلسة
إذا كنت ترسِل إحالات ناجحة غير إلكترونية أو إحالات ناجحة محسّنة للعملاء المحتملين، أضِف سمات الجلسة عندما لا تتوفّر معرّفات إعلانات أخرى، مثل معرّف النقرة من Google أو 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غير دقيقة أو جزئية، مثل سلسلة عنصر نائب أو مسار تطبيق داخلي أو عنوان URL غير مكتمل. احذفlanding_page_urlإذا لم يكن لديك عنوان 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"
إذا كانت وجهات حدث مُرسَل كمصدر بيانات إضافي تتضمّن موقعًا على "إحصاءات Google"، املأ الحقول التالية كما هو موضّح:
eventNameمَعلمة مطلوبة. اسم الحدث في "إحصاءات Google".
transactionIdمطلوب المعرّف الفريد للحدث.
- معرّف واحد على الأقل
يجب ضبط حقل واحد على الأقل من الحقول التالية:
clientId: معرّف فريد لمثيل مستخدم لبرنامج عميل على الويب. اطّلِع على إرسال حدث إلى Measurement Protocol.userId: معرّف فريد للمستخدم. لمزيد من المعلومات، يُرجى الاطّلاع على قياس النشاط على مختلف المنصّات باستخدام User-ID.
destinationReferencesمطلوبة إذا كانت قائمة
destinationsعلى مستوى الطلب تتضمّن أكثر منDestinationواحدة من "إحصاءات Google". أضِف إدخالاً إلىdestinationReferencesلتحديد وجهة "إحصاءات Google" التي يجب أن تتلقّى الحدث. اطّلِع على مقالة إرسال الأحداث إلى وجهات متعدّدة للحصول على مزيد من المعلومات عن مراجع الوجهات.إذا لم يتم ضبط
destinationReferencesأو كان يتضمّن عدّة إدخالات تشير إلى وجهات "إحصاءات Google"، سترفض واجهة برمجة التطبيقات Data Manager الحدث مع ظهور الخطأMULTIPLE_DESTINATIONS_FOR_GOOGLE_ANALYTICS_EVENT.userIdاختياريّ. User-ID الخاص بالمستخدم.
additionalEventParametersاختيارية، ولكن ننصح بها املأ هذه القائمة بأي مَعلمات أحداث في "إحصاءات Google" لم يتم تسجيلها في حقول
Eventالأخرى. يمكن أن تتضمّن المَعلمات مَعلمات إضافية يُنصَح بها من حدثpurchase، أو مَعلمات أخرى تريد تسجيلها. استخدِم اسم مَعلمة "إحصاءات Google" الخاص بـparameterNameفيEventParameter.على سبيل المثال، إذا كانت لديك الضرائب المرتبطة بمعاملة، أضِف إدخالاً إلى
additionalEventParametersمع ضبطparameterNameعلىtax، وضبطvalueعلى تكلفة الضريبة.لا ننصح بإضافة إدخالات لمعلمات أحداث "إحصاءات Google"
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" فيparameterNameالخاص بـItemParameter.على سبيل المثال، إذا كانت لديك العلامة التجارية والفئة الخاصة بمنتج، أضِف إدخالاً إلى
additionalItemParametersالخاص بالمنتج مع ضبطparameterNameعلىitem_brandوضبطvalueعلى اسم العلامة التجارية، وأضِف إدخالاً آخر مع ضبطparameterNameعلىitem_categoryوضبطvalueعلى فئة المنتج.لا ننصح بإضافة إدخالات لمعلمات عناصر "إحصاءات Google"
quantityأوpriceأوitem_id. بدلاً من ذلك، املأ الحقولitemIdوunitPriceوquantityفيItem، والتي لها الأولوية على أي إدخالات فيadditionalItemParameters.
في ما يلي نموذج Event للبيانات المنسّقة والمجزّأة والمشفّرة من الحدث الثاني، مع بيانات إضافية لخدمة "إحصاءات Google":
{
"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 وانقر على فتح في API Explorer لفتح API Explorer في علامة تبويب أو نافذة جديدة.
- في نص الطلب في API Explorer، استبدِل كل سلسلة تبدأ بـ
REPLACE_WITH، مثلREPLACE_WITH_OPERATING_ACCOUNT_TYPE، بالقيمة ذات الصلة. - انقر على تنفيذ في أسفل صفحة 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>(); } } }
جافا
// 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، يرسل Data Manager API الحدث إلى جميع الوجهات في الطلب.
إذا كان الحدث يتضمّن وجهات متعدّدة، يرسل Data Manager API الحقول ذات الصلة إلى كل وجهة. على سبيل المثال، إذا كان الحدث يتضمّن وجهة في "إعلانات Google" ووجهة في "إحصاءات Google"، يتضمّن واجهة برمجة التطبيقات حقول "إحصاءات Google"، مثل clientId أو eventName، عند إرسال الحدث إلى وجهة "إحصاءات Google"، ويتضمّن حقول "إعلانات Google"، مثل customVariables، عند إرسال الحدث إلى وجهة "إعلانات Google".
الخطوات التالية
- ضبط المصادقة وإعداد بيئتك باستخدام مكتبة برامج للعملاء
- تعرَّف على متطلبات التنسيق والتجزئة والترميز لكل نوع من أنواع البيانات.
- كيفية تشفير بيانات المستخدمين
- كيفية استرداد بيانات التشخيص لطلباتك
- مزيد من المعلومات حول أفضل الممارسات
- مزيد من المعلومات عن الحدود والحصص