با دنبال کردن این راهنما، با فرایند ارسال رویدادهای چندمنبعی Google Analytics با Data Manager API آشنا شوید.
در این راهنما، مراحل زیر را تکمیل میکنید:
Destinationرا برای دریافت دادههای رویداد آماده کنید.- آماده کردن دادههای رویداد برای ارسال.
- درخواست
IngestionServiceبرای رویدادها بساز. - درخواست را با Google APIs Explorer ارسال کنید.
- پاسخهای موفقیتآمیز و ناموفق را درک کنید.
آماده کردن مقصدها
قبلاز اینکه بتوانید داده ارسال کنید، باید حداقل یک
Destination برای دادهها آماده کنید. فیلدهای
Destination در اینجا آمده است. برای جزئیات بیشتر و نمونههایی از مقصدها برای سناریوهای مختلف، پیکربندی مقصدها را بررسی کنید.
operatingAccountدارایی Google Analytics که رویدادها را دریافت میکند.
accountTypeرا رویGOOGLE_ANALYTICS_PROPERTYوaccountIdرا روی شناسه دارایی Google Analytics (برای نمونه،123456789) تنظیم کنید.اطلاعات اعتباری درخواست باید برای «حساب Google» با نقش ویرایشگر یا سرپرست برای دارایی باشد.
loginAccountحسابی که کاربر اطلاعات اعتباری به آن دسترسی دارد.
accountTypeرا رویGOOGLE_ANALYTICS_PROPERTYوaccountIdرا روی شناسه دارایی Google Analytics تنظیم کنید.productDestinationIdشناسه جریان دادهای که رویدادها را دریافت میکند.
جریان دادههای وب
روی شناسه اندازهگیری جریان دادههای وب Google Analytics (با قالب
G-XXXXXXXXXX) تنظیم کنید.جریان دادههای برنامه
روی شناسه برنامه Firebase جریان داده برنامه Android یا iOS در Google Analytics تنظیم کنید.
{
"operatingAccount": {
"accountType": "GOOGLE_ANALYTICS_PROPERTY",
"accountId": "PROPERTY_ID"
},
"loginAccount": {
"accountType": "GOOGLE_ANALYTICS_PROPERTY",
"accountId": "LOGIN_ACCOUNT_ID"
},
"productDestinationId": "PRODUCT_DESTINATION_ID"
}
مثال این راهنما نشان میدهد که چگونه میتوان درخواستی ساخت که همه رویدادها را به مقصد یکسانی ارسال کند. اگر میخواهید رویدادها را برای چندین مقصد در یک درخواست ارسال کنید، به ارسال رویدادها برای چندین مقصد مراجعه کنید.
آماده کردن دادههای رویداد
قبلاز ارسال دادههای رویداد در درخواست، باید دادهها را آماده کنید:
- مقادیر خام را همانگونه که در راهنمای قالببندی مشخص شده است قالببندی کنید.
- شناسههای کاربر مثل نشانیهای ایمیل، نامهای کوچک، و نامهای خانوادگی باید بااستفاده از الگوریتم SHA-256 درهمسازی شوند و بااستفاده از درهمسازی هگزادسیمال (هگز) یا Base64 کدبندی شوند.
- پایهبار
Eventرا بااستفاده از مقادیر قالببندیشده و درهمسازیشده بسازید.
برگههای زیر دادههای رویداد نمونه را نشان میدهد که از ورودی خام، ازطریق قالببندی، تا مقدار بار نهایی پیشرفت میکند:
رویداد ۱
| مشخصه رویداد | مقدار خام | قالببندیشده | مقدار پایهبار |
|---|---|---|---|
event_timestamp |
June 10, 2025 3:07:01PM America/Chicago |
2025-06-10T15:07:01-05:00 (ISO 8601) |
2025-06-10T15:07:01-05:00 |
product_destination_id |
G-1234567890 |
G-1234567890 |
G-1234567890 |
event_name |
purchase |
purchase |
purchase |
client_id |
1234567890.1761581763 |
1234567890.1761581763 |
1234567890.1761581763 |
user_id |
user_ABC12345 |
user_ABC12345 |
user_ABC12345 |
transaction_id |
XYZ987654321 |
XYZ987654321 |
XYZ987654321 |
conversion_value |
30.03 |
30.03 |
30.03 |
currency |
usd |
USD (کد ISO سه حرفی) |
USD |
ad_unit_name |
Banner_01 |
Banner_01 |
Banner_01 |
| دادههای سبد خرید | |||
item_id |
SKU_12345 |
SKU_12345 |
SKU_12345 |
unit_price |
10.01 |
10.01 |
10.01 |
quantity |
3 |
3 |
3 |
| پارامترهای اضافی مورد | |||
item_name |
Stan and Friends Tee |
Stan and Friends Tee |
Stan and Friends Tee |
item_category |
Apparel |
Apparel |
Apparel |
item_brand |
Google |
Google |
Google |
رویداد ۲
| مشخصه رویداد | مقدار خام | قالببندیشده | مقدار پایهبار |
|---|---|---|---|
event_timestamp |
June 10, 2025 11:42:33PM America/New_York |
2025-06-10T23:42:33-05:00 (ISO 8601) |
2025-06-10T23:42:33-05:00 |
product_destination_id |
G-1234567890 |
G-1234567890 |
G-1234567890 |
event_name |
purchase |
purchase |
purchase |
client_id |
9876543210.1761582117 |
9876543210.1761582117 |
9876543210.1761582117 |
user_id |
user_DEF9876 |
user_DEF9876 |
user_DEF9876 |
transaction_id |
DEF999911111 |
DEF999911111 |
DEF999911111 |
conversion_value |
42.02 |
42.02 |
42.02 |
currency |
eur |
EUR (کد ISO سه حرفی) |
EUR |
email_address |
|
|
|
given_name |
zoë |
zoë (کوتاهشده، حروف کوچک) |
2752B88686847FA5C86F47B94CE652B7B3F22A91C37617D451A4DB9AFA431450 (هگز SHA-256) |
family_name |
pérez |
pérez (کوتاهشده، حروف کوچک) |
6654977D57DDDD3C0329CA741B109EF6CD6430BEDD00008AAD213DF25683D77F (هگز SHA-256) |
address_line |
1800 Amphibious Blvd. |
1800 amphibious blvd (حروف کوچک، بدون نشان سجاوندی) |
FF75E73A0E768CC1FA28A64FAEBBCECCB562D7C05F2FFCDD8D100ABAD73E4579 (هگز SHA-256) |
city |
Mountain View |
mountain view (کوتاهشده، حروف کوچک) |
mountain view |
administrative_area |
CA |
ca (کوتاهشده، حروف کوچک) |
ca |
region_code |
US |
US (کد ISO دوحرفی) |
US |
postal_code |
94043 |
94043 |
94043 |
customer_type |
RETURNING |
RETURNING |
RETURNING |
ad_unit_name |
Banner_02 |
Banner_02 |
Banner_02 |
| دادههای سبد خرید | |||
item_id |
SKU_12346 |
SKU_12346 |
SKU_12346 |
unit_price |
21.01 |
21.01 |
21.01 |
quantity |
2 |
2 |
2 |
| پارامترهای اضافی مورد | |||
item_name |
Google Grey Women's Tee |
Google Grey Women's Tee |
Google Grey Women's Tee |
item_category |
Apparel |
Apparel |
Apparel |
item_brand |
Google |
Google |
Google |
تبدیل دادهها به Event شیء
دادههای قالببندیشده و درهمسازیشده هر رویداد را به Event تبدیل کنید.
الزامات فیلد رویداد
رویدادهایتان را طبق الزامات جدول زیر بسازید.
برای فهرست کامل فیلدهای دردسترس، به سند مرجع Event مراجعه کنید.
| فیلد | وضعیت | شرح |
|---|---|---|
eventName |
الزامی | نام رویداد Google Analytics. اگر نام رویداد نام رزروشده باشد، «میانای برنامهسازی کاربردی مدیر داده» رویداد را با خطای INVALID_EVENT_NAME رد میکند. |
eventTimestamp |
الزامی | زمان وقوع رویداد. قالب مُهر زمان را ببینید. رویدادهای Google Analytics باید در ۷۲ ساعت گذشته eventTimestamp داشته باشند. علاوهبراین، باید رویدادهای Google Analytics را که میخواهید به آنها بپیوندید یا آنها را در کنار رویدادهایی که «کیت توسعه نرمافزار Google Analytics برای Firebase» یا gtag.js جمعآوری میکند پردازش کنید، ظرف ۴۸ ساعت از مُهر زمان رویداد سمت مشتری اصلی ارسال کنید. Google Analytics ممکن است رویدادهایی را که دیرتر از این زمان ارسال میشوند آنطور که انتظار میرود پردازش نکند، بهویژه برای اسناد تبدیل. |
transactionId |
الزامی | شناسه یکتای تراکنش. |
eventSource |
اختیاری | منشأ رویداد. اگر تنظیم شده باشد، باید WEB برای جاریسازی دادههای وب یا APP برای جاریسازی دادههای برنامه باشد. |
conversionValue |
اختیاری | مقدار پولی مرتبط با رویداد. |
currency |
اختیاری | کد ارز (مثل USD یا EUR) مرتبط با مقادیر پولی در این رویداد. |
cartData |
الزامی مشروط | اگر در مرجع رویداد Google Analytics برای رویداد مشخص شده باشد، الزامی است. افزودن دادههای سبد خرید را ببینید. |
additionalEventParameters |
اختیاری | اختیاری است، اما توصیه میشود. این فهرست را با هریک از پارامترهای رویداد Google Analytics که در Event فیلد دیگر ضبط نشده است تکمیل کنید. به افزودن پارامترهای رویداد مراجعه کنید. |
userProperties |
اختیاری | مشخصههای کاربر برای کاربر. افزودن داراییهای کاربر را ببینید. |
destinationReferences |
الزامی مشروط | اگر فهرست destinations سطح درخواست حاوی بیشاز یک Destination Google Analytics باشد، الزامی است. ارسال رویدادها برای چندین مقصد را ببینید. |
consent |
اختیاری | تنظیمات موافقت قانون بازارهای دیجیتال (DMA) برای کاربر، مشخص میکند که آیا برای adUserData و adPersonalization موافقت شده است یا نه. |
الزامات شناسه
در اینجا الزامات شناسه برای هر نوع جاریسازی داده آورده شده است:
جریان دادههای وب
هنگام ارسال رویداد چندمنبعی Google Analytics به جاریسازی دادههای وب، باید حداقل یکی از شناسههای زیر را اضافه کنید:
clientId: شناسه یکتا برای نمونه کاربر از کارخواه وب.- اگر از gtag.js استفاده میکنید، از gtag.js('get') برای دریافت مقدار
client_idاستفاده کنید. - اگر از Google Tag Manager استفاده میکنید:
- در اکثر موارد، از متغیر داخلی شناسه کارخواه Analytics استفاده کنید.
- اگر نیاز دارید
client_idرا در الگوی سفارشی بازیابی کنید، از readAnalyticsStorage API استفاده کنید.
- اگر از gtag.js استفاده میکنید، از gtag.js('get') برای دریافت مقدار
userId: شناسه یکتای کاربر. برای اطلاعات بیشتر، اندازهگیری فعالیت در پلاتفرمها با «شناسه کاربر» را ببینید.userData: دادههای ارائهشده توسط کاربر، مثل نشانی ایمیل، شماره تلفن، و نشانی، که نشاندهنده کاربر منسوب به رویداد است. برای راهنمایی درباره نحوه نرمالسازی و درهمسازی مقادیر، قالببندی دادههای کاربر را ببینید.
جریان دادههای برنامه
هنگام ارسال رویداد چندمنبعی Google Analytics به جریان داده برنامه، این الزامات را رعایت کنید:
appInstanceId: الزامی است. روی شناسه یکتا برای نمونه کاربر کارخواه برنامه تنظیم شود. باید بااستفاده از «کیت توسعه نرمافزار Firebase» بازیابی شود:userId: اختیاری. شناسه یکتا برای کاربر. برای اطلاعات بیشتر، اندازهگیری فعالیت در پلاتفرمهای مختلف با «شناسه کاربر» را ببینید.adIdentifiers: اختیاری. شناسههای کلیک باgbraidیاgclid.userData: اختیاری. دادههای ارائهشده توسط کاربر، مثل نشانی ایمیل، شماره تلفن، و نشانی، که نشاندهنده کاربر منسوب به رویداد است. برای راهنمایی درباره نحوه نرمالسازی و درهمسازی مقادیر، قالببندی دادههای کاربر را ببینید.
نحوه مدیریت دادههای چند منبعی توسط Google
در همان رویداد و دارایی، Google Analytics از
transactionId
برای حذف کردن رویداد تکراری از منابع مختلف (مثل برچسب وبسایت شما و
درخواستهای انتقال داده میانای برنامهسازی کاربردی «مدیر داده») استفاده میکند. Google Analytics از اطلاعات اولین نمونه رویداد یکسانی که دریافت کرده است استفاده میکند.
افزودن دادههای سبد خرید
فیلد cartData در Event
را با اطلاعات مربوط به موارد مرتبط با رویداد تکمیل کنید. از این فیلد هنگام ارسال رویدادهای توصیهشده Google Analytics مثل purchase که به فهرست items نیاز دارند استفاده کنید.
فیلدهای CartData
شیء در اینجا آمده است:
items- الزامی. حداقل یک
Itemدر این فهرست اضافه کنید.
فیلدهای مورد
یک یا چند شیء Item را به فهرست items
CartData اضافه کنید.
فیلدهای زیر را برای هر Item تکمیل کنید:
items.itemId- الزامی. شناسه یکتای مورد.
items.unitPriceاختیاری. قیمت واحد بدون احتساب مالیات، هزینه ارسال، و تخفیفهای محدوده رویداد (سطح تراکنش) برای این مورد.
اگر محصول تخفیف محدوده محصول دارد، از قیمت واحد تخفیفدار استفاده کنید. برای مثال، اگر قیمت واحد کالایی
27.67و تخفیف واحد آن6.66باشد،unitPriceرا روی21.01تنظیم کنید.items.quantityالزامی. تعداد واحدها برای این مورد خاص.
items.additionalItemParametersاختیاری. این فهرست را با هریک از پارامترهای سطح مورد که در
Itemفیلد دیگر ضبط نشده است تکمیل کنید. از نام پارامتر در اسناد Google Analytics بهعنوانparameterNameدرItemParameterاستفاده کنید.برای مثال، برای Google Analytics، اگر نمانام و دستهبندی محصولی را برای رویداد
purchaseدارید، ورودیای را بهadditionalItemParametersمحصول باparameterNameتنظیمشده رویitem_brandوvalueتنظیمشده روی نام نمانام اضافه کنید.توصیه نمیکنیم ورودیهایی را به
additionalItemParametersبرای پارامترهای موردquantity،price، یاitem_idاضافه کنید. درعوض، فیلدهایitemId،unitPrice، وquantityدرItemرا تکمیل کنید، که بر هر ورودی درadditionalItemParametersاولویت دارد.
افزودن پارامترهای رویداد
فهرست
additionalEventParameters
Event را با هریک از پارامترهای رویداد Google Analytics که در
فیلدهای دیگر Event ثبت نشده است تکمیل کنید. پارامترها میتوانند شامل
پارامترهای توصیهشده اضافی برای رویداد
یا هر پارامتر دیگری که میخواهید ضبط کنید باشند. از نام پارامتر Google Analytics برای
parameterName
EventParameter استفاده کنید.
برای مثال، اگر مالیاتهای مربوط به تراکنشی را دارید، ورودیای به additionalEventParameters اضافه کنید که parameterName روی tax تنظیم شده باشد،
و value روی هزینه مالیات تنظیم شده باشد.
توصیه نمیکنیم ورودیهایی را به additionalEventParameters برای
transactionId، currency، یا value پارامترهای رویداد Google Analytics اضافه کنید. درعوض،
فیلدهای transactionId، currency، و conversionValue را در
Event تکمیل کنید، که بر هر ورودی در
additionalEventParameters اولویت دارد.
افزودن دادههای ارائهشده توسط کاربر
فیلد userData Event را با اطلاعات مشتری طرف اول (مثل نشانی ایمیل، شماره تلفن، و نشانی) تکمیل کنید تا اندازهگیری تبدیل و اسنادی بهبود یابد.
قالببندی و درهمسازی: شناسههای شخصی (مثل نشانی ایمیل، شماره تلفن، نام، و نشانی خیابان) باید قبلاز ارسال، بااستفاده از SHA-256 عادیسازی و درهمسازی شوند. فیلدهای دیگر (مثل شهر، ایالت یا استان، کد پستی، و کد کشور) عادیسازی میشوند اما درهمسازی نمیشوند. برای الزامات ویژه هر شناسه، قالببندی دادههای کاربر را ببینید.
رمزگذاری: میتوانید بهصورت اختیاری شناسههای کاربر درهمسازیشده را رمزگذاری کنید.
User-ID: هنگام ارائه
userData، توصیه میکنیم درصورت دردسترس بودن شناسه کاربر یکسان، فیلدuserIdرا نیز ارائه دهید.الزام رویداد: در Data Manager API، دادههای ارائهشده توسط کاربر باید بهعنوان بخشی از
Eventارسال شود. بارگذاری دادههای کاربر مستقل بدون رویداد پشتیبانی نمیشود.
افزودن مشخصههای کاربر
مشخصههای کاربر
کاربر را در زمان رویداد توصیف میکنند. برای هر دارایی کاربر، ورودی جداگانهای به
additionalUserProperties
فهرست اضافه کنید.
هنگام ارسال مشخصههای کاربر، از نامهای مشخصه کاربر رزروشده زیر استفاده نکنید:
first_open_timefirst_visit_timelast_deep_link_referreruser_idfirst_open_after_install
علاوهبراین، از پیشوندهای زیر برای نامهای دارایی کاربر استفاده نکنید:
google_ga_firebase_
درخواستهای نمونه
در اینجا نمونههایی از Event شیء برای دادههای قالببندیشده از هر رویداد آورده شده است:
رویداد ۱
{
"additionalEventParameters": [
{
"parameterName": "ad_unit_name",
"value": "Banner_01"
}
],
"cartData": {
"items": [
{
"additionalItemParameters": [
{
"parameterName": "item_name",
"value": "Stan and Friends Tee"
},
{
"parameterName": "item_category",
"value": "Apparel"
},
{
"parameterName": "item_brand",
"value": "Google"
}
],
"itemId": "SKU_12345",
"quantity": 3,
"unitPrice": 10.01
}
]
},
"clientId": "1234567890.1761581763",
"conversionValue": 30.03,
"currency": "USD",
"eventName": "purchase",
"eventTimestamp": "2025-06-10T15:07:01-05:00",
"transactionId": "XYZ987654321",
"userId": "user_ABC12345"
}
رویداد ۲
{
"additionalEventParameters": [
{
"parameterName": "ad_unit_name",
"value": "Banner_02"
}
],
"cartData": {
"items": [
{
"additionalItemParameters": [
{
"parameterName": "item_name",
"value": "Google Grey Women's Tee"
},
{
"parameterName": "item_category",
"value": "Apparel"
},
{
"parameterName": "item_brand",
"value": "Google"
}
],
"itemId": "SKU_12346",
"quantity": 2,
"unitPrice": 21.01
}
]
},
"clientId": "9876543210.1761582117",
"conversionValue": 42.02,
"currency": "EUR",
"eventName": "purchase",
"eventTimestamp": "2025-06-10T23:42:33-05:00",
"transactionId": "DEF999911111",
"userData": {
"userIdentifiers": [
{
"emailAddress": "3E693CF7E5B67880BFF33B2D2626DADB7BF1D4BC737192E47CF8BAA89ACF2250"
},
{
"emailAddress": "223EBDA6F6889B1494551BA902D9D381DAF2F642BAE055888E96343D53E9F9C4"
},
{
"address": {
"addressLine": "FF75E73A0E768CC1FA28A64FAEBBCECCB562D7C05F2FFCDD8D100ABAD73E4579",
"administrativeArea": "ca",
"city": "mountain view",
"familyName": "6654977D57DDDD3C0329CA741B109EF6CD6430BEDD00008AAD213DF25683D77F",
"givenName": "2752B88686847FA5C86F47B94CE652B7B3F22A91C37617D451A4DB9AFA431450",
"postalCode": "94043",
"regionCode": "US"
}
}
]
},
"userId": "user_DEF9876",
"userProperties": {
"additionalUserProperties": [
{
"propertyName": "customer_type",
"value": "RETURNING"
}
]
}
}
ساختن متن درخواست
برای ساختن متن درخواست، destinations و events را ترکیب کنید، فیلد encoding را تنظیم کنید، و هر فیلد درخواست دیگری را که میخواهید اضافه کنید، مثل validateOnly و consent.
ارسال درخواست
در اینجا مراحل امتحان کردن درخواست از مرورگرتان آمده است:
- برگه REST را انتخاب کنید و روی باز کردن در «کاوشگر API» کلیک کنید تا «کاوشگر API» در برگه یا پنجره جدیدی باز شود.
- در بدنه درخواست در «کاوشگر API»، هر رشتهای را که با
REPLACE_WITHشروع میشود، مثلREPLACE_WITH_OPERATING_ACCOUNT_TYPE، با مقدار مربوطه جایگزین کنید. - در پایین صفحه «کاوشگر API»، روی اجرا کلیک کنید و پیاموارههای مجوز را برای ارسال درخواست تکمیل کنید.
-
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": "PRODUCT_DESTINATION_ID" } ], "encoding": "HEX", "consent": { "adUserData": "CONSENT_GRANTED", "adPersonalization": "CONSENT_GRANTED" }, "events": [ { "additionalEventParameters": [ { "parameterName": "ad_unit_name", "value": "Banner_01" } ], "cartData": { "items": [ { "additionalItemParameters": [ { "parameterName": "item_name", "value": "Stan and Friends Tee" }, { "parameterName": "item_category", "value": "Apparel" }, { "parameterName": "item_brand", "value": "Google" } ], "itemId": "SKU_12345", "quantity": 3, "unitPrice": 10.01 } ] }, "clientId": "1234567890.1761581763", "conversionValue": 30.03, "currency": "USD", "eventName": "purchase", "eventTimestamp": "2025-06-10T15:07:01-05:00", "transactionId": "XYZ987654321", "userId": "user_ABC12345" }, { "additionalEventParameters": [ { "parameterName": "ad_unit_name", "value": "Banner_02" } ], "cartData": { "items": [ { "additionalItemParameters": [ { "parameterName": "item_name", "value": "Google Grey Women's Tee" }, { "parameterName": "item_category", "value": "Apparel" }, { "parameterName": "item_brand", "value": "Google" } ], "itemId": "SKU_12346", "quantity": 2, "unitPrice": 21.01 } ] }, "clientId": "9876543210.1761582117", "conversionValue": 42.02, "currency": "EUR", "eventName": "purchase", "eventTimestamp": "2025-06-10T23:42:33-05:00", "transactionId": "DEF999911111", "userData": { "userIdentifiers": [ { "emailAddress": "3E693CF7E5B67880BFF33B2D2626DADB7BF1D4BC737192E47CF8BAA89ACF2250" }, { "emailAddress": "223EBDA6F6889B1494551BA902D9D381DAF2F642BAE055888E96343D53E9F9C4" }, { "address": { "addressLine": "FF75E73A0E768CC1FA28A64FAEBBCECCB562D7C05F2FFCDD8D100ABAD73E4579", "administrativeArea": "ca", "city": "mountain view", "familyName": "6654977D57DDDD3C0329CA741B109EF6CD6430BEDD00008AAD213DF25683D77F", "givenName": "2752B88686847FA5C86F47B94CE652B7B3F22A91C37617D451A4DB9AFA431450", "postalCode": "94043", "regionCode": "US" } } ] }, "userId": "user_DEF9876", "userProperties": { "additionalUserProperties": [ { "propertyName": "customer_type", "value": "RETURNING" } ] } } ], "validateOnly": true }
دات نت
// 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}"); if (response.FieldWarnings.Any()) { Console.WriteLine( "Request ingested successfully, but field warnings were returned. " + "Review warning details and update your implementation as needed." ); } } 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 #%d:%n%s", requestCount, response)); if (response.getFieldWarningsCount() > 0) { LOGGER.warning( "Request ingested successfully, but field warnings were returned. Review warning" + " details and update your implementation as needed."); } } 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/datamanager-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); if (response.fieldWarnings && response.fieldWarnings.length > 0) { console.warn( 'Request ingested successfully, but field warnings were returned. ' + 'Review warning details and update your implementation as needed.', ); } } 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"; if (count($response->getFieldWarnings()) > 0) { echo 'Request ingested successfully, but field warnings were returned. ' . "Review warning details and update your implementation as needed.\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) if response.field_warnings: _logger.warning( "Request ingested successfully, but field warnings were returned. " "Review warning details and update your implementation as needed." ) _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 برمیگرداند.
اگر هریک از فیلدهای اختیاری درستیسنجی نشد، پاسخ شامل
fieldWarnings
فهرست نیز میشود.
پاسخ استاندارد
در اینجا یک پاسخ نمونه برای درخواست موفقیتآمیز انتقال داده بدون هشدار آورده شده است:
{
"requestId": "126365e1-16d0-4c81-9de9-f362711e250a"
}
پاسخ با هشدار
در اینجا یک پاسخ نمونه برای درخواست موفقیتآمیز انتقال داده که حاوی هشدار است آورده شده است:
{
"requestId": "126365e1-16d0-4c81-9de9-f362711e250a",
"fieldWarnings": [
{
"field": "events.events[0].cart_data.items[0].merchant_product_id",
"description": "The merchant product ID is missing in the cart item.",
"reason": "WARNING_REASON_CART_DATA_ITEM_MERCHANT_PRODUCT_ID_MISSING"
}
]
}
requestId برگشتی را ضبط کنید تا بتوانید
تشخیص خرابی را با پردازش هر مقصد در درخواست
بازیابی کنید. همچنین باید
fieldWarnings
را بررسی کنید تا مطمئن شوید فیلدهای اختیاری که ارسال کردهاید پذیرفته شده است. برای جزئیات بیشتر، هشدارهای
انتقال داده را ببینید.
پاسخهای ناموفق
درخواست ناموفق منجر به کد وضعیت پاسخ خطا مانند 400 Bad
Request و پاسخی با جزئیات خطا میشود.
Data Manager API از
مدل سریعشکست استفاده میکند:
اگر هر رویدادی درستیسنجی نشود، کل درخواست ناموفق خواهد بود و هیچیک از
رویدادها پردازش نخواهند شد. خطاها را برطرف کنید و کل درخواست را دوباره ارسال کنید. برای
بررسی این خطاها بدون انتقال داده، درخواست را با
validateOnly تنظیمشده روی true ارسال کنید.
برای مثال، 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": "123456789",
"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 رویداد را به همه مقصدها در
درخواست ارسال میکند.
اگر رویدادی چندین مقصد داشته باشد، «میانای برنامهسازی کاربردی مدیر دادهها» فیلدهای مربوطه را به هر مقصد ارسال میکند. برای مثال، اگر رویدادی مقصد Google Ads و مقصد
Google Analytics داشته باشد، وقتی رویداد به مقصد Google Analytics ارسال میشود، API شامل فیلدهای Google Analytics مثل clientId،
appInstanceId، یا eventName میشود، و وقتی رویداد به مقصد Google Ads ارسال میشود، شامل فیلدهای Google Ads مثل customVariables میشود.
مراحل بعدی
- اصالتسنجی را پیکربندی کنید و محیطتان را با کتابخانه کارخواه راهاندازی کنید.
- درباره الزامات قالببندی، درهمسازی، و کدبندی برای هر نوع داده بیشتر بدانید.
- با نحوه رمزگذاری دادههای کاربر آشنا شوید.
- با نحوه بازیابی تشخیص خرابی برای درخواستهایتان آشنا شوید.
- با روالهای مطلوب آشنا شوید.
- درباره حدود و سهمیهها بیشتر بدانید.