อ่านคู่มือนี้เพื่อทำความคุ้นเคยกับกระบวนการส่งเหตุการณ์จากหลายแหล่งที่มาของ Google Analytics ด้วย Data Manager API
ในคู่มือนี้ คุณจะต้องทำตามขั้นตอนต่อไปนี้
- เตรียม
Destinationเพื่อรับข้อมูลเหตุการณ์ - เตรียมข้อมูลเหตุการณ์ที่จะส่ง
- สร้างคำขอ
IngestionServiceสำหรับกิจกรรม - ส่งคำขอด้วยโปรแกรมสำรวจ Google APIs
- ทำความเข้าใจการตอบกลับที่สำเร็จและไม่สำเร็จ
เตรียมปลายทาง
ก่อนที่จะส่งข้อมูล คุณต้องเตรียม Destination อย่างน้อย 1 รายการสำหรับข้อมูลดังกล่าว ช่องของ Destination มีดังนี้ ดูรายละเอียดเพิ่มเติมและตัวอย่างปลายทางสำหรับสถานการณ์ต่างๆ ได้ที่กำหนดค่าปลายทาง
operatingAccountพร็อพเพอร์ตี้ Google Analytics ที่ได้รับเหตุการณ์ ตั้งค่า
accountTypeเป็นGOOGLE_ANALYTICS_PROPERTYและaccountIdเป็นรหัสพร็อพเพอร์ตี้ Google Analytics (เช่น123456789)ข้อมูลเข้าสู่ระบบของคำขอต้องเป็นของบัญชี Google ที่มีบทบาทเป็นผู้แก้ไขหรือผู้ดูแลระบบสำหรับพร็อพเพอร์ตี้
loginAccountบัญชีที่ผู้ใช้ของข้อมูลเข้าสู่ระบบมีสิทธิ์เข้าถึง ตั้งค่า
accountTypeเป็นGOOGLE_ANALYTICS_PROPERTYและaccountIdเป็นรหัสของพร็อพเพอร์ตี้ Google AnalyticsproductDestinationIdตัวระบุของสตรีมข้อมูลที่รับเหตุการณ์
สตรีมข้อมูลเว็บ
ตั้งค่าเป็นรหัสการวัดของสตรีมข้อมูลเว็บ Google Analytics (ในรูปแบบ
G-XXXXXXXXXX)สตรีมข้อมูลแอป
ตั้งค่าเป็นรหัสแอป Firebase ของสตรีมข้อมูลแอป iOS หรือ Android ของ 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 และเข้ารหัสโดยใช้การเข้ารหัสเลขฐาน 16 (Hex) หรือ Base64
- สร้างเพย์โหลด
Eventโดยใช้ค่าที่จัดรูปแบบและค่าแฮช
แท็บต่อไปนี้แสดงตัวอย่างข้อมูลเหตุการณ์ที่เปลี่ยนจากอินพุตดิบ ผ่านการจัดรูปแบบ ไปจนถึงค่าเพย์โหลดสุดท้าย
เหตุการณ์ 1
| แอตทริบิวต์เหตุการณ์ | ค่าดิบ | จัดรูปแบบแล้ว | ค่าเพย์โหลด |
|---|---|---|---|
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 3 ตัวอักษร) |
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 |
เหตุการณ์ 2
| แอตทริบิวต์เหตุการณ์ | ค่าดิบ | จัดรูปแบบแล้ว | ค่าเพย์โหลด |
|---|---|---|---|
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 3 ตัวอักษร) |
EUR |
email_address |
|
|
|
given_name |
zoë |
zoë (ตัดช่องว่าง, ตัวพิมพ์เล็ก) |
2752B88686847FA5C86F47B94CE652B7B3F22A91C37617D451A4DB9AFA431450 (SHA-256 hex) |
family_name |
pérez |
pérez (ตัดช่องว่าง, ตัวพิมพ์เล็ก) |
6654977D57DDDD3C0329CA741B109EF6CD6430BEDD00008AAD213DF25683D77F (SHA-256 hex) |
address_line |
1800 Amphibious Blvd. |
1800 amphibious blvd (ตัวพิมพ์เล็ก ไม่มีเครื่องหมายวรรคตอน) |
FF75E73A0E768CC1FA28A64FAEBBCECCB562D7C05F2FFCDD8D100ABAD73E4579 (SHA-256 hex) |
city |
Mountain View |
mountain view (ตัดช่องว่าง, ตัวพิมพ์เล็ก) |
mountain view |
administrative_area |
CA |
ca (ตัดช่องว่าง, ตัวพิมพ์เล็ก) |
ca |
region_code |
US |
US (รหัส ISO 2 ตัวอักษร) |
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 หากชื่อเหตุการณ์เป็นชื่อที่สงวนไว้ Data Manager API จะปฏิเสธเหตุการณ์พร้อมข้อผิดพลาด INVALID_EVENT_NAME |
eventTimestamp |
จำเป็น | เวลาที่เกิดเหตุการณ์ ดูรูปแบบการประทับเวลา เหตุการณ์สําหรับ Google Analytics ต้องมี eventTimestamp ภายใน 72 ชั่วโมงที่ผ่านมา นอกจากนี้ คุณต้องส่งเหตุการณ์ Google Analytics ที่ต้องการรวมหรือประมวลผลควบคู่ไปกับเหตุการณ์ที่ Google Analytics สำหรับ Firebase SDK หรือ gtag.js รวบรวมภายใน 48 ชั่วโมงนับจากการประทับเวลาเหตุการณ์ฝั่งไคลเอ็นต์เดิม Google Analytics อาจไม่ประมวลผลเหตุการณ์ที่ส่งหลังจากนี้ตามที่คาดไว้ โดยเฉพาะอย่างยิ่งสําหรับการระบุแหล่งที่มาของ Conversion |
transactionId |
จำเป็น | ตัวระบุที่ไม่ซ้ำกันสำหรับธุรกรรม |
eventSource |
ไม่บังคับ | ต้นทางของเหตุการณ์ หากตั้งค่าไว้ ต้องเป็น WEB สำหรับสตรีมข้อมูลเว็บ หรือ APP สำหรับสตรีมข้อมูลแอป |
conversionValue |
ไม่บังคับ | มูลค่าทางการเงินที่เชื่อมโยงกับเหตุการณ์ |
currency |
ไม่บังคับ | รหัสสกุลเงิน (เช่น USD หรือ EUR) ที่เชื่อมโยงกับมูลค่าทางการเงินในเหตุการณ์นี้ |
cartData |
ต้องระบุแบบมีเงื่อนไข | ต้องระบุหากระบุไว้ในข้อมูลอ้างอิงเหตุการณ์ Google Analytics สำหรับเหตุการณ์นั้นๆ ดูหัวข้อเพิ่มข้อมูลรถเข็นช็อปปิ้ง |
additionalEventParameters |
ไม่บังคับ | ไม่บังคับแต่แนะนำ ป้อนพารามิเตอร์เหตุการณ์ Google Analytics ที่ไม่ได้บันทึกไว้ในช่อง Event อื่นๆ ลงในรายการนี้ ดูเพิ่มพารามิเตอร์เหตุการณ์ |
userProperties |
ไม่บังคับ | พร็อพเพอร์ตี้ผู้ใช้สำหรับผู้ใช้ ดูเพิ่มพร็อพเพอร์ตี้ผู้ใช้ |
destinationReferences |
ต้องระบุแบบมีเงื่อนไข | ต้องระบุหากรายการ destinations ระดับคำขอมี Destination ของ Google Analytics มากกว่า 1 รายการ ดูส่งเหตุการณ์สําหรับปลายทางหลายรายการ |
consent |
ไม่บังคับ | การตั้งค่าความยินยอมตามกฎหมายตลาดดิจิทัล (DMA) สําหรับผู้ใช้ ซึ่งระบุว่ามีการให้ความยินยอมสําหรับ adUserData และ adPersonalization หรือไม่ |
ข้อกำหนดเกี่ยวกับตัวระบุ
ข้อกำหนดของตัวระบุสำหรับสตรีมข้อมูลแต่ละประเภทมีดังนี้
สตรีมข้อมูลเว็บ
คุณต้องระบุตัวระบุต่อไปนี้อย่างน้อย 1 รายการเมื่อส่งเหตุการณ์ Google Analytics ที่มีหลายแหล่งที่มาไปยังสตรีมข้อมูลเว็บ
clientId: ตัวระบุที่ไม่ซ้ำกันสำหรับอินสแตนซ์ผู้ใช้ของไคลเอ็นต์เว็บ- หากใช้ gtag.js ให้ใช้ gtag.js('get') เพื่อรับค่า
client_id - หากคุณใช้ Google Tag Manager ให้ทำดังนี้
- ในกรณีส่วนใหญ่ ให้ใช้ตัวแปรในตัวรหัสไคลเอ็นต์ Analytics
- หากต้องการดึงข้อมูล
client_idภายในเทมเพลตที่กำหนดเอง ให้ใช้ API readAnalyticsStorage
- หากใช้ gtag.js ให้ใช้ gtag.js('get') เพื่อรับค่า
userId: ตัวระบุที่ไม่ซ้ำกันสำหรับผู้ใช้ ดูข้อมูลเพิ่มเติมได้ที่วัดกิจกรรมในแพลตฟอร์มต่างๆ ด้วย User-IDuserData: ข้อมูลที่ได้จากผู้ใช้ เช่น อีเมล หมายเลขโทรศัพท์ และที่อยู่ ซึ่งแสดงถึงผู้ใช้ที่เชื่อมโยงกับเหตุการณ์ ดูคำแนะนำเกี่ยวกับวิธีแปลงค่าเป็นรูปแบบมาตรฐานและแฮชค่าได้ที่จัดรูปแบบข้อมูลผู้ใช้
สตรีมข้อมูลแอป
ปฏิบัติตามข้อกำหนดต่อไปนี้เมื่อส่งเหตุการณ์ Google Analytics ที่มีหลายแหล่งที่มาไปยังสตรีมข้อมูลแอป
appInstanceId: ต้องระบุ ตั้งค่าเป็นตัวระบุที่ไม่ซ้ำกันสำหรับอินสแตนซ์ผู้ใช้ของไคลเอ็นต์แอป ต้องดึงข้อมูลโดยใช้ Firebase SDK:userId: ไม่บังคับ ตัวระบุที่ไม่ซ้ำกันสำหรับผู้ใช้ ดูข้อมูลเพิ่มเติมได้ที่วัดกิจกรรมในแพลตฟอร์มต่างๆ ด้วย User-IDadIdentifiers: ไม่บังคับ ตัวระบุการคลิกที่มีgbraidหรือgcliduserData: ไม่บังคับ ข้อมูลที่ได้จากผู้ใช้ เช่น อีเมล หมายเลขโทรศัพท์ และที่อยู่ ซึ่งแสดงถึงผู้ใช้ที่เชื่อมโยงกับเหตุการณ์ ดูคำแนะนำเกี่ยวกับวิธีแปลงค่าเป็นรูปแบบมาตรฐานและแฮชค่าได้ที่จัดรูปแบบข้อมูลผู้ใช้
วิธีที่ Google จัดการข้อมูลจากหลายแหล่ง
ภายในเหตุการณ์และพร็อพเพอร์ตี้เดียวกัน Google Analytics จะใช้
transactionId
เพื่อกรองเหตุการณ์ที่ซ้ำกันออกจากแหล่งที่มาต่างๆ (เช่น แท็กเว็บไซต์และคำขอส่งผ่านข้อมูล Data Manager API) Google Analytics จะใช้ข้อมูลจากอินสแตนซ์แรกของเหตุการณ์เดียวกันที่ได้รับ
เพิ่มข้อมูลรถเข็นช็อปปิ้ง
ป้อนข้อมูลเกี่ยวกับสินค้าที่เชื่อมโยงกับเหตุการณ์ในช่อง cartData ของ Event ใช้ช่องนี้เมื่อส่งเหตุการณ์ที่แนะนําของ Google Analytics เช่น purchase ที่ต้องใช้รายการ items
ฟิลด์ของออบเจ็กต์ CartData มีดังนี้
items- ต้องระบุ ใส่
Itemอย่างน้อย 1 รายการในรายการนี้
ฟิลด์สินค้า
เพิ่มออบเจ็กต์ Item อย่างน้อย 1 รายการลงในรายการ items ของ CartData
ป้อนข้อมูลในช่องต่อไปนี้สำหรับ Item แต่ละรายการ
items.itemId- ต้องระบุ ตัวระบุที่ไม่ซ้ำกันสำหรับรายการ
items.unitPriceไม่บังคับ ราคาต่อหน่วยไม่รวมภาษี ค่าจัดส่ง และส่วนลดที่กำหนดขอบเขตระดับเหตุการณ์ (ระดับธุรกรรม) สำหรับสินค้านี้
หากสินค้ามีส่วนลดระดับสินค้า ให้ใช้ราคาต่อหน่วยที่ลดแล้ว เช่น หากสินค้ามีราคาต่อหน่วยเป็น
27.67และมีส่วนลดต่อหน่วยเป็น6.66ให้ตั้งค่าunitPriceเป็น21.01items.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 สำหรับพารามิเตอร์เหตุการณ์ Google Analytics transactionId, currency หรือ value แต่ให้ป้อนข้อมูลในช่อง transactionId, currency และ conversionValue ของ Event ซึ่งจะมีความสำคัญเหนือกว่ารายการใดๆ ใน additionalEventParameters
เพิ่มข้อมูลที่ได้จากผู้ใช้
ป้อนข้อมูลในช่อง userData ของ Event ด้วยข้อมูลลูกค้าบุคคลที่หนึ่ง (เช่น อีเมล หมายเลขโทรศัพท์ และที่อยู่) เพื่อปรับปรุงการวัด Conversion และการระบุแหล่งที่มา
การจัดรูปแบบและการแฮช: ตัวระบุส่วนบุคคล (เช่น อีเมล หมายเลขโทรศัพท์ ชื่อ และที่อยู่) ต้องได้รับการแปลงเป็นรูปแบบมาตรฐานและแฮชโดยใช้ 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 สำหรับข้อมูลที่จัดรูปแบบจากแต่ละเหตุการณ์มีดังนี้
เหตุการณ์ 1
{
"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"
}
เหตุการณ์ 2
{
"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 }
.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}"); 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>(); } } }
Java
// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.ads.datamanager.samples; import com.beust.jcommander.Parameter; import com.google.ads.datamanager.samples.common.BaseParamsConfig; import com.google.ads.datamanager.util.UserDataFormatter; import com.google.ads.datamanager.util.UserDataFormatter.Encoding; import com.google.ads.datamanager.v1.AdIdentifiers; import com.google.ads.datamanager.v1.Consent; import com.google.ads.datamanager.v1.ConsentStatus; import com.google.ads.datamanager.v1.Destination; import com.google.ads.datamanager.v1.Event; import com.google.ads.datamanager.v1.EventSource; import com.google.ads.datamanager.v1.IngestEventsRequest; import com.google.ads.datamanager.v1.IngestEventsResponse; import com.google.ads.datamanager.v1.IngestionServiceClient; import com.google.ads.datamanager.v1.ProductAccount; import com.google.ads.datamanager.v1.ProductAccount.AccountType; import com.google.ads.datamanager.v1.UserData; import com.google.ads.datamanager.v1.UserIdentifier; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.reflect.TypeToken; import com.google.gson.GsonBuilder; import com.google.protobuf.util.Timestamps; import java.io.BufferedReader; import java.io.IOException; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; /** * Sends an {@link IngestEventsRequest} without using encryption. * * <p>Event data is read from a data file. See the {@code events_1.json} file in the {@code * resources/sampledata} directory for a sample file. */ public class IngestEvents { private static final Logger LOGGER = Logger.getLogger(IngestEvents.class.getName()); /** The maximum number of events allowed per request. */ private static final int MAX_EVENTS_PER_REQUEST = 2_000; private static final class ParamsConfig extends BaseParamsConfig<ParamsConfig> { @Parameter( names = "--operatingAccountType", required = true, description = "Account type of the operating account") AccountType operatingAccountType; @Parameter( names = "--operatingAccountId", required = true, description = "ID of the operating account") String operatingAccountId; @Parameter( names = "--loginAccountType", required = false, description = "Account type of the login account") AccountType loginAccountType; @Parameter( names = "--loginAccountId", required = false, description = "ID of the login account") String loginAccountId; @Parameter( names = "--linkedAccountType", required = false, description = "Account type of the linked account") AccountType linkedAccountType; @Parameter( names = "--linkedAccountId", required = false, description = "ID of the linked account") String linkedAccountId; @Parameter( names = "--conversionActionId", required = true, description = "ID of the conversion action") String conversionActionId; @Parameter( names = "--jsonFile", required = true, description = "JSON file containing user data to ingest") String jsonFile; @Parameter( names = "--validateOnly", required = false, arity = 1, description = "Whether to enable validateOnly on the request") boolean validateOnly = true; } public static void main(String[] args) throws IOException { ParamsConfig paramsConfig = new ParamsConfig().parseOrExit(args); if ((paramsConfig.loginAccountId == null) != (paramsConfig.loginAccountType == null)) { throw new IllegalArgumentException( "Must specify either both or neither of login account ID and login account type"); } if ((paramsConfig.linkedAccountId == null) != (paramsConfig.linkedAccountType == null)) { throw new IllegalArgumentException( "Must specify either both or neither of linked account ID and linked account type"); } new IngestEvents().runExample(paramsConfig); } /** * Runs the example. This sample assumes that the login and operating account are the same. * * @param params the parameters for the example */ private void runExample(ParamsConfig params) throws IOException { // Reads event data from the JSON file. List<EventRecord> eventRecords = readEventData(params.jsonFile); // Gets an instance of the UserDataFormatter for normalizing and formatting the data. UserDataFormatter userDataFormatter = UserDataFormatter.create(); // Builds the events collection for the request. List<Event> events = new ArrayList<>(); for (EventRecord eventRecord : eventRecords) { Event.Builder eventBuilder = Event.newBuilder(); try { eventBuilder.setEventTimestamp(Timestamps.parse(eventRecord.timestamp)); } catch (ParseException pe) { LOGGER.warning( () -> String.format("Skipping event with invalid timestamp: %s", eventRecord.timestamp)); continue; } if (Strings.isNullOrEmpty(eventRecord.transactionId)) { LOGGER.warning("Skipping event with no transaction ID"); continue; } eventBuilder.setTransactionId(eventRecord.transactionId); if (!Strings.isNullOrEmpty(eventRecord.eventSource)) { try { eventBuilder.setEventSource(EventSource.valueOf(eventRecord.eventSource)); } catch (IllegalArgumentException iae) { LOGGER.warning("Skipping event with invalid event source: " + eventRecord.eventSource); continue; } } if (!Strings.isNullOrEmpty(eventRecord.gclid)) { eventBuilder.setAdIdentifiers(AdIdentifiers.newBuilder().setGclid(eventRecord.gclid)); } if (!Strings.isNullOrEmpty(eventRecord.currency)) { eventBuilder.setCurrency(eventRecord.currency); } if (eventRecord.value != null) { eventBuilder.setConversionValue(eventRecord.value); } UserData.Builder userDataBuilder = UserData.newBuilder(); // Adds a UserIdentifier for each valid email address for the eventRecord. if (eventRecord.emails != null) { for (String email : eventRecord.emails) { String preparedEmail; try { preparedEmail = userDataFormatter.processEmailAddress(email, Encoding.HEX); } catch (IllegalArgumentException iae) { // Skips invalid input. continue; } // Sets the email address identifier to the encoded email hash. userDataBuilder.addUserIdentifiers( UserIdentifier.newBuilder().setEmailAddress(preparedEmail)); } } // Adds a UserIdentifier for each valid phone number for the eventRecord. if (eventRecord.phoneNumbers != null) { for (String phoneNumber : eventRecord.phoneNumbers) { String preparedPhoneNumber; try { preparedPhoneNumber = userDataFormatter.processPhoneNumber(phoneNumber, Encoding.HEX); } catch (IllegalArgumentException iae) { // Skips invalid input. continue; } // Sets the phone number identifier to the encoded phone number hash. userDataBuilder.addUserIdentifiers( UserIdentifier.newBuilder().setPhoneNumber(preparedPhoneNumber)); } } if (userDataBuilder.getUserIdentifiersCount() > 0) { eventBuilder.setUserData(userDataBuilder); } events.add(eventBuilder.build()); } // Builds the Destination for the request. Destination.Builder destinationBuilder = Destination.newBuilder() .setOperatingAccount( ProductAccount.newBuilder() .setAccountType(params.operatingAccountType) .setAccountId(params.operatingAccountId)) .setProductDestinationId(params.conversionActionId); if (params.loginAccountType != null && params.loginAccountId != null) { destinationBuilder.setLoginAccount( ProductAccount.newBuilder() .setAccountType(params.loginAccountType) .setAccountId(params.loginAccountId)); } if (params.linkedAccountType != null && params.linkedAccountId != null) { destinationBuilder.setLinkedAccount( ProductAccount.newBuilder() .setAccountType(params.linkedAccountType) .setAccountId(params.linkedAccountId)); } try (IngestionServiceClient ingestionServiceClient = IngestionServiceClient.create()) { int requestCount = 0; // Batches requests to send up to the maximum number of events per request. for (List<Event> eventsBatch : Lists.partition(events, MAX_EVENTS_PER_REQUEST)) { requestCount++; // Builds the request. IngestEventsRequest request = IngestEventsRequest.newBuilder() .addDestinations(destinationBuilder) // Adds events from the current batch. .addAllEvents(eventsBatch) .setConsent( Consent.newBuilder() .setAdPersonalization(ConsentStatus.CONSENT_GRANTED) .setAdUserData(ConsentStatus.CONSENT_GRANTED)) // Sets validate_only. If true, then the Data Manager API only validates the request // but doesn't apply changes. .setValidateOnly(params.validateOnly) // Sets encoding to match the encoding used. .setEncoding(com.google.ads.datamanager.v1.Encoding.HEX) .build(); LOGGER.info(() -> String.format("Request:%n%s", request)); IngestEventsResponse response = ingestionServiceClient.ingestEvents(request); LOGGER.info(String.format("Response for request #%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 ใช้โมเดล Fast-Fail กล่าวคือ หากเหตุการณ์ใดไม่ผ่านการตรวจสอบความถูกต้อง คำขอทั้งหมดจะล้มเหลวและระบบจะไม่ประมวลผลเหตุการณ์ใดๆ เลย โปรดแก้ไขข้อผิดพลาดและส่งคำขอทั้งหมดอีกครั้ง หากต้องการตรวจสอบข้อผิดพลาดเหล่านี้โดยไม่ต้องนำเข้าข้อมูล ให้ส่งคำขอโดยตั้งค่า 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 ที่ไม่ได้แฮชและเข้ารหัสฐาน 16 เท่านั้นจะสร้างการตอบกลับต่อไปนี้
{
"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 ให้ส่งทั้ง 2 เหตุการณ์ในคําขอเดียวโดยตั้งค่า 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 เพื่อส่งไปยังปลายทางที่เฉพาะเจาะจงอย่างน้อย 1 แห่ง ตัวอย่างเช่น 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 Ads และปลายทาง Google Analytics, API จะรวมช่อง Google Analytics เช่น clientId, appInstanceId หรือ eventName เมื่อส่งเหตุการณ์ไปยังปลายทาง Google Analytics และรวมช่อง Google Ads เช่น customVariables เมื่อส่งเหตุการณ์ไปยังปลายทาง Google Ads
ขั้นตอนถัดไป
- กำหนดค่าการตรวจสอบสิทธิ์และตั้งค่าสภาพแวดล้อมด้วยไลบรารีของไคลเอ็นต์
- ดูข้อมูลเกี่ยวกับข้อกำหนดการจัดรูปแบบ การแฮช และการเข้ารหัสสำหรับข้อมูลแต่ละประเภท
- ดูวิธีเข้ารหัสข้อมูลผู้ใช้
- ดูวิธีเรียกข้อมูลการวินิจฉัยสำหรับคำขอของคุณ
- ดูข้อมูลเกี่ยวกับแนวทางปฏิบัติแนะนำ
- ดูข้อมูลเกี่ยวกับขีดจำกัดและโควต้า