ส่งกิจกรรม

คุณสามารถทำตามการเริ่มต้นอย่างรวดเร็วนี้เพื่อทำความคุ้นเคยกับการส่งข้อมูลเหตุการณ์

ใช้ Data Manager API สำหรับสถานการณ์ต่อไปนี้

  • ส่ง Conversion ของแท็ก Google Ads หรือเหตุการณ์ purchase ของ Google Analytics เป็น แหล่งข้อมูลเพิ่มเติมสําหรับ Conversion ของแท็ก เพื่อเพิ่มสัญญาณการโต้ตอบกับโฆษณา และเสริมความแข็งแกร่งให้กับข้อมูลและประสิทธิภาพโดยรวม

    ฟีเจอร์นี้พร้อมให้บริการในบัญชี Google Ads ทุกบัญชี แต่ใช้ได้เฉพาะกับ พร็อพเพอร์ตี้ Google Analytics ในรายการที่อนุญาตเท่านั้น กรอกแบบฟอร์มหากสนใจเพิ่มพร็อพเพอร์ตี้ Google Analytics ลงในรายการที่อนุญาต

  • ส่งข้อมูลเหตุการณ์สําหรับ Conversion ออฟไลน์ของ Google Ads หรือ Conversion ที่ปรับปรุงแล้วสําหรับโอกาสในการขาย

เลือกเวอร์ชันของคำแนะนำที่ต้องการดู

  • เลือกผู้ลงโฆษณาหากคุณใช้ข้อมูลเข้าสู่ระบบสำหรับบัญชี Google ที่เป็นผู้ใช้ในบัญชีผู้ลงโฆษณาที่คุณต้องการจัดการ
  • เลือกพาร์ทเนอร์ข้อมูลหากคุณใช้ข้อมูลเข้าสู่ระบบสำหรับบัญชี Google ที่เป็นผู้ใช้ในบัญชีพาร์ทเนอร์ข้อมูล และต้องการจัดการบัญชีผู้ลงโฆษณาที่มีลิงก์พาร์ทเนอร์กับบัญชีพาร์ทเนอร์ข้อมูล

ในการเริ่มต้นอย่างรวดเร็วนี้ คุณจะได้ทำตามขั้นตอนต่อไปนี้

  1. เตรียม Destination เพื่อรับข้อมูลเหตุการณ์
  2. เตรียมข้อมูลเหตุการณ์ที่จะส่ง
  3. สร้างคำขอ IngestionService สำหรับ เหตุการณ์
  4. ส่งคำขอด้วย Google APIs Explorer
  5. ทำความเข้าใจการตอบกลับที่สำเร็จและไม่สำเร็จ

เตรียมปลายทาง

คุณต้องเตรียมDestinationอย่างน้อย 1 รายการสำหรับข้อมูลก่อนจึงจะส่งข้อมูลได้ นี่คือDestinationตัวอย่างที่คุณใช้ได้

    {
      "operatingAccount": {
        "accountType": "OPERATING_ACCOUNT_TYPE",
        "accountId": "OPERATING_ACCOUNT_ID"
      },

      "loginAccount": {
        "accountType": "LOGIN_ACCOUNT_TYPE",
        "accountId": "LOGIN_ACCOUNT_ID"
      },

      "productDestinationId": "PRODUCT_DESTINATION_ID"
    }

ฟิลด์ของ Destination มีดังนี้ ดูรายละเอียดและตัวอย่างเพิ่มเติมเกี่ยวกับปลายทางสำหรับสถานการณ์ต่างๆ ได้ที่กำหนดค่า ปลายทาง

operatingAccount

บัญชีที่รับเหตุการณ์

สําหรับเหตุการณ์ที่ส่งเป็นแหล่งข้อมูลเพิ่มเติม บัญชีที่ดําเนินการอาจเป็นบัญชี Google Ads หรือพร็อพเพอร์ตี้ Google Analytics ก็ได้

หาก accountType เป็น GOOGLE_ANALYTICS_PROPERTY ข้อมูลเข้าสู่ระบบของคำขอ ต้องเป็นของผู้ใช้ Google Analytics ที่มีบทบาทผู้แก้ไขหรือผู้ดูแลระบบสำหรับพร็อพเพอร์ตี้

สําหรับ Conversion ออฟไลน์และ Conversion ที่ปรับปรุงแล้วสําหรับโอกาสในการขาย บัญชีปฏิบัติการ ต้องเป็นบัญชี Google Ads

loginAccount
บัญชีที่บัญชี Google สำหรับข้อมูลเข้าสู่ระบบเป็นผู้ใช้
productDestinationId

รหัสของเอนทิตีใน operatingAccount ที่ได้รับเหตุการณ์

สําหรับเหตุการณ์ที่ส่งเป็นแหล่งข้อมูลเพิ่มเติม productDestinationId ต้องเป็นอย่างใดอย่างหนึ่งต่อไปนี้

  1. รหัสของ Conversion ของ Google Ads ที่มี type ตั้งค่าเป็น WEBPAGE ใน UI ของ Google Ads แหล่งที่มาของ Conversion สำหรับ การกระทำที่ถือเป็น Conversion WEBPAGE คือ เว็บไซต์

  2. รหัสการวัดของ สตรีมเว็บ Google Analytics คุณจะส่งเหตุการณ์เป็นแหล่งข้อมูลเพิ่มเติมไปยังสตรีมแอป iOS หรือแอป Android ของ Google Analytics ไม่ได้

สําหรับ Conversion ออฟไลน์หรือ Conversion ที่ปรับปรุงแล้วสําหรับโอกาสในการขาย productDestinationId ต้องเป็นรหัสของการกระทำที่ถือเป็น Conversion ของ Google Ads ที่มี type ตั้งค่าเป็น UPLOAD_CLICKS ใน UI ของ Google Ads แหล่งที่มาของ Conversion สําหรับการกระทําที่ถือเป็น Conversion UPLOAD_CLICKS คือ เว็บไซต์ (นําเข้าจากการคลิก)

ตัวอย่างในคู่มือนี้แสดงวิธีสร้างคำขอที่ส่งทุกเหตุการณ์ไปยังปลายทางเดียวกัน หากต้องการส่งเหตุการณ์ไปยังปลายทางหลายแห่งในคำขอเดียวกัน โปรดดูส่งเหตุการณ์ไปยังปลายทางหลายแห่ง

เตรียมข้อมูลเหตุการณ์

พิจารณาข้อมูลเหตุการณ์ต่อไปนี้ แต่ละตารางจะสอดคล้องกับเหตุการณ์ Conversion หนึ่งรายการ เหตุการณ์ Conversion แต่ละรายการจะมีแสตมป์เวลาของเหตุการณ์ การกระทำที่ถือเป็น Conversion และมูลค่า Conversion

เหตุการณ์แต่ละรายการอาจมีตัวระบุโฆษณา เช่น gclid หรือตัวระบุผู้ใช้ เช่น อีเมล หมายเลขโทรศัพท์ และข้อมูลที่อยู่ นอกจากนี้ กิจกรรมยังอาจมีสิ่งต่อไปนี้ด้วย

  • ข้อมูลเกี่ยวกับผู้ใช้ที่ประเมิน ณ เวลาที่เกิดเหตุการณ์ เช่น มูลค่าของลูกค้า หรือไม่ว่าจะเป็นลูกค้าใหม่ ลูกค้าที่กลับมา หรือลูกค้าที่กลับมามีส่วนร่วมอีกครั้ง
  • ข้อมูลรถเข็นช็อปปิ้ง
  • พารามิเตอร์เหตุการณ์หรือพร็อพเพอร์ตี้ผู้ใช้เพิ่มเติมสําหรับปลายทาง เช่น client_id หรือ user_id สําหรับ Google Analytics

ข้อมูลเหตุการณ์มีดังนี้

เหตุการณ์ 1

กิจกรรม #1
conversion_time 2025-06-10 15:07:01-05:00
conversion_action_id 123456789
transaction_id ABC798654321
conversion_value 30.03
currency USD
gclid GCLID_1
emails
given_name John
family_name Smith-Jones
region_code us
postal_code 94045
customer_type NEW
customer_value_bucket HIGH
client_id 1234567890.1761581763
user_id user_ABC12345
ad_unit_name Banner_01
event_name purchase
สินค้าในรถเข็น
item_id SKU_12345
item_name Stan and Friends Tee
item_affiliation Google Merchandise Store
item_coupon SUMMER_FUN
item_discount 2.22
item_index 0
item_brand Google
item_category Apparel
item_category2 Adult
item_category3 Shirts
item_category4 Crew
item_category5 Short sleeve
item_list_id related_products
item_list_name Related Products
item_price 10.01
item_quantity 3

เหตุการณ์ 2

เหตุการณ์ #2
conversion_time June 10, 2025 11:42:33PM America/New_York
conversion_action_id 123456789
transaction_id DEF999911111
conversion_value 42.02
currency eur
gclid GCLID_2
emails

zoe@EXAMPLE.COM

cloudy.sanfrancisco@gmail.com

given_name zoë
family_name pérez
region_code PT
postal_code 1229-076
customer_type RETURNING
client_id 9876543210.1761582117
user_id user_DEF9876
ad_unit_name Banner_02
event_name purchase
สินค้าในรถเข็น
item_id SKU_12346
item_name Google Grey Women's Tee
item_affiliation Google Merchandise Store
item_coupon SUMMER_FUN
item_discount 3.33
item_index 1
item_brand Google
item_category Apparel
item_category2 Adult
item_category3 Shirts
item_category4 Crew
item_category5 Short sleeve
item_list_id related_products
item_list_name Related Products
item_price 21.01
item_quantity 2

จัดรูปแบบข้อมูล

จัดรูปแบบฟิลด์ตามที่ระบุไว้ในคำแนะนำ การจัดรูปแบบ ข้อมูลเหตุการณ์หลังการจัดรูปแบบมีดังนี้

เหตุการณ์ 1

กิจกรรม #1
conversion_time 2025-06-10T15:07:01-05:00
conversion_action_id 123456789
transaction_id ABC798654321
conversion_value 30.03
currency USD
gclid GCLID_1
emails
given_name john
family_name smith-jones
region_code US
postal_code 94045
customer_type NEW
customer_value_bucket HIGH
client_id 1234567890.1761581763
user_id user_ABC12345
ad_unit_name Banner_01
event_name purchase
สินค้าในรถเข็น
item_id SKU_12345
item_name Stan and Friends Tee
item_affiliation Google Merchandise Store
item_coupon SUMMER_FUN
item_discount 2.22
item_index 0
item_brand Google
item_category Apparel
item_category2 Adult
item_category3 Shirts
item_category4 Crew
item_category5 Short sleeve
item_list_id related_products
item_list_name Related Products
item_price 10.01
item_quantity 3

เหตุการณ์ 2

เหตุการณ์ #2
conversion_time 2025-06-10T23:42:33-05:00
conversion_action_id 123456789
transaction_id DEF999911111
conversion_value 42.02
currency EUR
gclid GCLID_2
emails

zoe@example.com

cloudysanfrancisco@gmail.com

given_name zoë
family_name pérez
region_code PT
postal_code 1229-076
customer_type RETURNING
client_id 9876543210.1761582117
user_id user_DEF9876
ad_unit_name Banner_02
event_name purchase
สินค้าในรถเข็น
item_id SKU_12346
item_name Google Grey Women's Tee
item_affiliation Google Merchandise Store
item_coupon SUMMER_FUN
item_discount 3.33
item_index 1
item_brand Google
item_category Apparel
item_category2 Adult
item_category3 Shirts
item_category4 Crew
item_category5 Short sleeve
item_list_id related_products
item_list_name Related Products
item_price 21.01
item_quantity 2

แฮชและเข้ารหัสข้อมูล

นอกจากนี้ ที่อยู่อีเมล ชื่อจริง และนามสกุลที่จัดรูปแบบแล้วต้อง ได้รับการแฮชโดยใช้อัลกอริทึม SHA-256 และเข้ารหัสโดยใช้การเข้ารหัสฐานสิบหกหรือ Base64 นี่คือข้อมูลเหตุการณ์หลังจากจัดรูปแบบ แฮช และเข้ารหัส โดยใช้การเข้ารหัสฐานสิบหก

เหตุการณ์ 1

กิจกรรม #1
conversion_time 2025-06-10T15:07:01-05:00
conversion_action_id 123456789
transaction_id ABC798654321
conversion_value 30.03
currency USD
gclid GCLID_1
emails
given_name 96D9632F363564CC3032521409CF22A852F2032EEC099ED5967C0D000CEC607A
family_name DB98D2607EFFFA28AFF66975868BF54C075ECA7157E35064DCE08E20B85B1081
region_code US
postal_code 94045
customer_type NEW
customer_value_bucket HIGH
client_id 1234567890.1761581763
user_id user_ABC12345
ad_unit_name Banner_01
event_name purchase
สินค้าในรถเข็น
item_id SKU_12345
item_name Stan and Friends Tee
item_affiliation Google Merchandise Store
item_coupon SUMMER_FUN
item_discount 2.22
item_index 0
item_brand Google
item_category Apparel
item_category2 Adult
item_category3 Shirts
item_category4 Crew
item_category5 Short sleeve
item_list_id related_products
item_list_name Related Products
item_price 10.01
item_quantity 3

เหตุการณ์ 2

เหตุการณ์ #2
conversion_time 2025-06-10T23:42:33-05:00
conversion_action_id 123456789
transaction_id DEF999911111
conversion_value 42.02
currency EUR
gclid GCLID_2
emails

3E693CF7E5B67880BFF33B2D2626DADB7BF1D4BC737192E47CF8BAA89ACF2250

223EBDA6F6889B1494551BA902D9D381DAF2F642BAE055888E96343D53E9F9C4

given_name 2752B88686847FA5C86F47B94CE652B7B3F22A91C37617D451A4DB9AFA431450
family_name 6654977D57DDDD3C0329CA741B109EF6CD6430BEDD00008AAD213DF25683D77F
region_code PT
postal_code 1229-076
customer_type RETURNING
client_id 9876543210.1761582117
user_id user_DEF9876
ad_unit_name Banner_02
event_name purchase
สินค้าในรถเข็น
item_id SKU_12346
item_name Google Grey Women's Tee
item_affiliation Google Merchandise Store
item_coupon SUMMER_FUN
item_discount 3.33
item_index 1
item_brand Google
item_category Apparel
item_category2 Adult
item_category3 Shirts
item_category4 Crew
item_category5 Short sleeve
item_list_id related_products
item_list_name Related Products
item_price 21.01
item_quantity 2

แปลงข้อมูลเป็นออบเจ็กต์ Event

แปลงข้อมูลที่จัดรูปแบบและแฮชของแต่ละเหตุการณ์เป็น Event กรอกข้อมูลใน ช่องต่อไปนี้ตามที่ระบุ

  1. ตั้งค่า eventTimestamp เป็นเวลาที่เกิดเหตุการณ์

    เหตุการณ์สําหรับ Google Analytics ต้องมี eventTimestamp ภายใน 72 ชั่วโมงที่ผ่านมา

  2. ตั้งค่าช่องที่ต้องกรอกสำหรับกรณีการใช้งาน

    กรณีการใช้งาน ตัวระบุ transactionId eventSource
    Conversion ออฟไลน์หรือ Conversion ที่ปรับปรุงแล้วสำหรับโอกาสในการขาย ต้องระบุ ตั้งค่าอย่างน้อย 1 รายการต่อไปนี้ ไม่บังคับ ต้องระบุ ตั้งค่าเป็นค่า enum ค่าใดค่าหนึ่งสำหรับ EventSource
    เหตุการณ์ที่ส่งเป็นแหล่งข้อมูลเพิ่มเติมไปยังปลายทาง Google Ads ต้องระบุ ตั้งค่าอย่างน้อย 1 รายการต่อไปนี้
    • adIdentifiers โดยตั้งค่า gclid, gbraid หรือ wbraid อย่างน้อย 1 รายการ
    • userData
    จำเป็น ไม่บังคับ หากตั้งค่าไว้ ต้องเป็น WEB
    เหตุการณ์ที่ส่งเป็นแหล่งข้อมูลเพิ่มเติมไปยังปลายทาง Google Analytics ต้องระบุ ตั้งค่าอย่างน้อย 1 รายการต่อไปนี้ จำเป็น ไม่บังคับ หากตั้งค่าไว้ ต้องเป็น WEB
  3. หากคุณส่งเหตุการณ์เป็นแหล่งข้อมูลเพิ่มเติมไปยังปลายทาง Google Ads โปรดอ่านวิธีที่ Google จัดการข้อมูลแหล่งข้อมูลเพิ่มเติม

  4. ป้อนข้อมูลในช่องอื่นๆ ที่คุณมีค่าสำหรับเหตุการณ์ โปรดดูรายการช่องทั้งหมดที่พร้อมใช้งานในเอกสารอ้างอิงของ Event

วิธีที่ Google จัดการข้อมูลแหล่งข้อมูลเพิ่มเติม

ในการกระทำที่ถือเป็น Conversion เดียวกัน Google จะใช้ transactionId เพื่อ ขจัดเหตุการณ์ Conversion ที่ซ้ำกันซึ่งส่งจากแหล่งที่มาต่างๆ (เช่น แท็กเว็บไซต์ และคำขอการนำเข้า Data Manager API) ตารางต่อไปนี้อธิบายวิธีประมวลผลข้อมูล จากคำขอส่งผ่านข้อมูล

สถานการณ์ ฟิลด์ข้อมูล วิธีจัดการ
transactionId ตรงกับเหตุการณ์แท็กที่มีอยู่ conversionValue (พร้อม currencyCode)

อัปเดตแล้ว conversionValue (มี currencyCode) จาก Event จะลบล้างค่าเดิมที่บันทึกโดยแท็ก

หมายเหตุ: ในระยะทดลอง 14 วันแรก สำหรับการกระทำที่ถือเป็น Conversion ระบบจะปิดใช้การอัปเดตมูลค่า ระบบจะไม่ลบล้างค่าของแท็กในการรายงานของ Google Ads จนกว่าระยะทดลองจะสิ้นสุด

transactionId ตรงกับเหตุการณ์แท็กที่มีอยู่ ฟิลด์อื่นๆ ยกเว้น conversionValue หรือ currencyCode (เช่น adIdentifiers.gclid) ไม่สนใจ มูลค่าในฟิลด์อื่นๆ จากแหล่งข้อมูลเพิ่มเติม จะไม่เขียนทับมูลค่าในฟิลด์ที่แท็ก Google บันทึกไว้เดิมสำหรับ ธุรกรรมที่ตรงกัน
transactionId ไม่ตรงกับเหตุการณ์ที่มีอยู่ ข้อมูลทั้งหมดที่ระบุ (เช่น userData, conversionValue, currencyCode)

ใช้เพื่อสร้างเหตุการณ์ Conversion ใหม่ จากนั้น Google จะพยายาม ระบุแหล่งที่มาของ Conversion ใหม่นี้เป็นการคลิกโฆษณาโดยใช้ตัวระบุที่คุณ ให้ (เช่น adIdentifiers.gclid หรือ userData)

หมายเหตุ: ในระยะทดลอง 14 วันแรก Conversion ที่สร้างขึ้นใหม่เหล่านี้จะปรากฏในการรายงาน แต่จะไม่ นำไปใช้ในการเสนอราคา หลังจากช่วงทดลองสิ้นสุดลง Conversion เหล่านั้นจะเสนอราคาได้โดยอัตโนมัติ

เพิ่มแอตทริบิวต์เซสชัน

หากคุณส่ง Conversion ออฟไลน์หรือ Conversion ที่ปรับปรุงแล้วสําหรับโอกาสในการขาย ให้เพิ่มแอตทริบิวต์เซสชันเมื่อตัวระบุโฆษณาอื่นๆ เช่น GCLID หรือ WBRAID ไม่พร้อมใช้งาน นอกจากนี้ คุณยังรวมแอตทริบิวต์เซสชันไว้ด้วยได้ นอกเหนือจากตัวระบุโฆษณาอื่นๆ

แอตทริบิวต์เซสชันให้บริบทและสัญญาณเพิ่มเติมเกี่ยวกับการโต้ตอบของผู้ใช้กับเว็บไซต์ ซึ่งช่วยเพิ่มความแม่นยำในการวัด Conversion, การรายงาน และการเสนอราคา

ใน Data Manager API คุณสามารถใช้ 2 วิธีต่อไปนี้เพื่อส่งแอตทริบิวต์เซสชัน

  1. แนะนํา: ตั้งค่าฟิลด์ sessionAttributes ของ adIdentifiers เป็นสตริงแอตทริบิวต์เซสชันที่เข้ารหัส base64 ทําตามวิธีการในวิธีบันทึก session_attributes เพื่อแก้ไขหน้าการส่งแบบฟอร์ม เพื่อบันทึกสตริงที่เข้ารหัส

  2. หากใช้ JavaScript ไม่ได้ ให้บันทึกฟิลด์แอตทริบิวต์เซสชันแต่ละรายการ และเพิ่มแต่ละรายการลงในรายการ experimentalFields เป็น ExperimentalField แยกกัน

    • gad_campaignid
    • session_start_time_usec
    • gad_source
    • landing_page_url
    • landing_page_referrer

    หากมีค่าสำหรับแอตทริบิวต์เซสชัน landing_page_user_agent ให้ส่งค่าในฟิลด์ userAgent ของ adIdentifiers.landingPageDeviceInfo

    แนวทางปฏิบัติแนะนำเมื่อส่งคู่คีย์-ค่าแต่ละคู่มีดังนี้

    • ส่ง gad_campaignid และ session_start_time_usec อย่างสม่ำเสมอ ฟิลด์เหล่านี้ มีความสำคัญอย่างยิ่งต่อการระบุแหล่งที่มาที่ถูกต้อง
    • อย่าระบุค่า landing_page_url ที่ไม่ถูกต้องหรือบางส่วน เช่น สตริงตัวยึดตำแหน่ง เส้นทางแอปพลิเคชันภายใน หรือ URL ที่ไม่สมบูรณ์ ละเว้น landing_page_url หากคุณไม่มี URL ที่ถูกต้องและครบถ้วน

    ต่อไปนี้คือส่วนหนึ่งของเหตุการณ์ตัวอย่างที่มีรายการใน experimentalFields สำหรับ gad_campaignid และ session_start_time_usec รวมถึง User Agent ในช่อง landingPageDeviceInfo

    {
      ...,
      "experimentalFields": [
        {
          "field": "gad_campaignid",
          "value": "21288051566"
        },
        {
          "field": "session_start_time_usec",
          "value": "1767711548052000"
        }
      ],
      "adIdentifiers": {
        "landingPageDeviceInfo": {
          "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
        }
      }
    }
    
    

เพิ่มข้อมูล Google Analytics

หากปลายทางของเหตุการณ์ที่ส่งเป็นแหล่งข้อมูลเพิ่มเติมมีพร็อพเพอร์ตี้ Google Analytics ให้ป้อนข้อมูลในช่องต่อไปนี้ตามที่ระบุ

eventName

ต้องระบุ ชื่อเหตุการณ์ Google Analytics

transactionId

ต้องระบุ ตัวระบุที่ไม่ซ้ำกันสำหรับเหตุการณ์

ตัวระบุอย่างน้อย 1 รายการ

ต้องตั้งค่าในช่องต่อไปนี้อย่างน้อย 1 ช่อง

destinationReferences

ต้องระบุหากรายการ destinations ระดับคำขอมี Google Analytics Destination มากกว่า 1 รายการ เพิ่มรายการไปยัง destinationReferences เพื่อระบุ ปลายทาง Google Analytics ที่ควรรับเหตุการณ์ ดูข้อมูลเพิ่มเติมเกี่ยวกับ การอ้างอิงปลายทางได้ที่หัวข้อส่งเหตุการณ์ไปยัง ปลายทางหลายแห่ง

หากไม่ได้ตั้งค่า destinationReferences หรือมีหลายรายการที่อ้างอิงถึง ปลายทาง Google Analytics API ของ Data Manager จะปฏิเสธเหตุการณ์โดยมีข้อผิดพลาด MULTIPLE_DESTINATIONS_FOR_GOOGLE_ANALYTICS_EVENT

userId

ไม่บังคับ User-ID สำหรับผู้ใช้

additionalEventParameters

ไม่บังคับ แต่แนะนำ สร้างรายการนี้ด้วยพารามิเตอร์เหตุการณ์ Google Analytics ที่ไม่ได้บันทึกไว้ในช่อง Event อื่นๆ พารามิเตอร์ อาจรวมพารามิเตอร์เพิ่มเติมที่แนะนําจากpurchase เหตุการณ์ หรือพารามิเตอร์อื่นๆ ที่คุณต้องการบันทึก ใช้ชื่อพารามิเตอร์ Google Analytics สําหรับ parameterName ของ EventParameter

เช่น หากคุณมีภาษีที่เชื่อมโยงกับธุรกรรม ให้เพิ่มรายการลงใน additionalEventParameters โดยตั้งค่า parameterName เป็น tax และตั้งค่า value เป็นค่าภาษี

เราไม่แนะนําให้เพิ่มรายการสําหรับพารามิเตอร์เหตุการณ์ transactionId, currency หรือ value ของ Google Analytics แต่ให้ป้อนข้อมูล transactionId, currency และ conversionValue ของ Event แทน ซึ่งจะมีลำดับความสำคัญเหนือกว่า รายการใดๆ ใน additionalEventParameters

เพิ่มข้อมูลรถเข็นช็อปปิ้งสําหรับเหตุการณ์การซื้อ

ระบุข้อมูลเกี่ยวกับสินค้าที่ซื้อในฟิลด์ cartData ของ Event สําหรับสินค้าแต่ละรายการที่ซื้อ ให้เพิ่มออบเจ็กต์ Item ลงในรายการ items ของ CartData และป้อนข้อมูลในช่องต่อไปนี้ตามที่ระบุ

itemId
ต้องระบุ ตัวระบุที่ไม่ซ้ำกันสำหรับสินค้า
unitPrice

ต้องระบุ ราคาต่อหน่วยไม่รวมภาษี ค่าจัดส่ง และส่วนลดระดับเหตุการณ์ (ระดับธุรกรรม)

หากสินค้ามีส่วนลดระดับสินค้า ให้ใช้ราคาต่อหน่วยที่มีส่วนลด เช่น หากสินค้ามีราคาต่อหน่วยเป็น 27.67 และส่วนลดต่อหน่วยเป็น 6.66 ให้ตั้งค่า unitPrice เป็น 21.01

quantity

ต้องระบุ จํานวนหน่วยที่ซื้อสําหรับสินค้ารายการนี้

additionalItemParameters

สร้างรายการนี้ด้วยพารามิเตอร์ระดับสินค้าที่ไม่ได้บันทึกไว้ในฟิลด์ Item อื่นๆ ใช้ชื่อพารามิเตอร์รายการ Google Analytics สําหรับ parameterName ของ ItemParameter

เช่น หากคุณมีแบรนด์และหมวดหมู่ของสินค้า ให้เพิ่มรายการลงใน additionalItemParameters ของสินค้าโดยตั้งค่า parameterName เป็น item_brand และตั้งค่า value เป็นชื่อแบรนด์ และเพิ่มอีกรายการโดยตั้งค่า parameterName เป็น item_category และตั้งค่า value เป็นหมวดหมู่ของสินค้า

เราไม่แนะนําให้เพิ่มรายการสําหรับพารามิเตอร์รายการ quantity, price หรือ item_id Google Analytics แต่ให้ป้อนข้อมูล itemId, unitPrice และ quantity ของ Item แทน ซึ่งจะมีลำดับความสำคัญเหนือกว่ารายการใดๆ ใน additionalItemParameters

นี่คือตัวอย่าง Event สําหรับข้อมูลที่จัดรูปแบบ แฮช และเข้ารหัสจากเหตุการณ์ที่ 2 พร้อมข้อมูลเพิ่มเติมสําหรับ Google Analytics

{
  "adIdentifiers": {
     "gclid": "GCLID_2"
  },
  "conversionValue": 42.02,
  "currency": "EUR",
  "eventTimestamp": "2025-06-10T23:42:33-05:00",
  "transactionId": "DEF999911111",
  "eventSource": "WEB",
  "userData": {
    "userIdentifiers": [
      {
        "emailAddress": "3E693CF7E5B67880BFF33B2D2626DADB7BF1D4BC737192E47CF8BAA89ACF2250"
      },
      {
        "emailAddress": "223EBDA6F6889B1494551BA902D9D381DAF2F642BAE055888E96343D53E9F9C4"
      },
      {
        "address": {
          "givenName": "2752B88686847FA5C86F47B94CE652B7B3F22A91C37617D451A4DB9AFA431450",
          "familyName": "6654977D57DDDD3C0329CA741B109EF6CD6430BEDD00008AAD213DF25683D77F",
          "regionCode": "PT",
          "postalCode": "1229-076"
        }
      }
    ],
  },
  "userProperties": {
    "customerType": "RETURNING"
  },
  "eventName": "purchase",
  "clientId": "9876543210.1761582117",
  "userId": "user_DEF9876",
  "additionalEventParameters": [
    {
      "parameterName": "ad_unit_name",
      "value": "Banner_02"
    }
  ],
  "cartData": {
    "transactionDiscount": 6.66,
    "items": [
      {
        "itemId": "SKU_12346",
        "quantity": 2,
        "unitPrice": 21.01,
        "additionalItemParameters": [
          {
            "parameterName": "item_name",
            "value": "Google Grey Women's Tee"
          },
          {
            "parameterName": "affiliation",
            "value": "Google Merchandise Store"
          },
          {
            "parameterName": "coupon",
            "value": "SUMMER_FUN"
          },
          {
            "parameterName": "discount",
            "value": "3.33"
          },
          {
            "parameterName": "index",
            "value": "1"
          },
          {
            "parameterName": "item_brand",
            "value": "Google"
          },
          {
            "parameterName": "item_category",
            "value": "Apparel"
          },
          {
            "parameterName": "item_category2",
            "value": "Adult"
          },
          {
            "parameterName": "item_category3",
            "value": "Shirts"
          },
          {
            "parameterName": "item_category4",
            "value": "Crew"
          },
          {
            "parameterName": "item_category5",
            "value": "Short sleeve"
          },
          {
            "parameterName": "item_list_id",
            "value": "related_products"
          },
          {
            "parameterName": "item_list_name",
            "value": "Related Products"
          }
        ]
      }
    ]
  }
}

สร้างเนื้อหาคำขอ

หากต้องการสร้างเนื้อหาคำขอ ให้รวม destinations และ events ตั้งค่าฟิลด์ encoding และเพิ่มฟิลด์คำขออื่นๆ ที่ต้องการรวม เช่น validateOnly และ consent

ตัวอย่างในคู่มือนี้ไม่ได้ใช้การเข้ารหัส แต่คุณสามารถทำตาม วิธีการในเข้ารหัสข้อมูลผู้ใช้เพื่อเพิ่มการเข้ารหัสลงใน กระบวนการได้

ส่งคำขอ

ขั้นตอนในการลองส่งคำขอจากเบราว์เซอร์มีดังนี้

  1. เลือกแท็บ REST แล้วคลิกเปิดใน API Explorer เพื่อเปิด API Explorer ในแท็บหรือหน้าต่างใหม่
  2. ในเนื้อหาคำขอใน API Explorer ให้แทนที่สตริงแต่ละรายการที่ขึ้นต้นด้วย REPLACE_WITH เช่น REPLACE_WITH_OPERATING_ACCOUNT_TYPE ด้วยค่าที่เกี่ยวข้อง
  3. คลิกดำเนินการที่ด้านล่างของหน้าโปรแกรมสำรวจ API แล้วทำตามข้อความแจ้งการให้สิทธิ์เพื่อส่งคำขอ
  4. ตั้งค่า validateOnly เป็น true เพื่อตรวจสอบคำขอโดยไม่ใช้การเปลี่ยนแปลง เมื่อพร้อมที่จะใช้การเปลี่ยนแปลงแล้ว ให้ตั้งค่า validateOnly เป็น false

หากติดตั้งไลบรารีของไคลเอ็นต์ ให้เลือกแท็บสำหรับภาษาโปรแกรมที่เลือกเพื่อดูตัวอย่างโค้ดที่สมบูรณ์ เกี่ยวกับวิธีสร้างและส่งคำขอ

REST

{
    "destinations": [
        {
            "operatingAccount": {
                "accountType": "OPERATING_ACCOUNT_TYPE",
                "accountId": "OPERATING_ACCOUNT_ID"
            },
            "loginAccount": {
                "accountType": "LOGIN_ACCOUNT_TYPE",
                "accountId": "LOGIN_ACCOUNT_ID"
            },
            "productDestinationId": "CONVERSION_ACTION_ID"
        }
    ],
    "encoding": "HEX",
    "events": [
        {
            "adIdentifiers": {
                "gclid": "GCLID_1"
            },
            "conversionValue": 30.03,
            "currency": "USD",
            "eventTimestamp": "2025-06-10T20:07:01Z",
            "transactionId": "ABC798654321",
            "eventSource": "WEB",
            "userData": {
                "userIdentifiers": [
                    {
                        "address": {
                            "givenName": "96D9632F363564CC3032521409CF22A852F2032EEC099ED5967C0D000CEC607A",
                            "familyName": "DB98D2607EFFFA28AFF66975868BF54C075ECA7157E35064DCE08E20B85B1081",
                            "regionCode": "US",
                            "postalCode": "94045"
                        }
                    }
                ]
            },
            "userProperties": {
                "customerType": "NEW",
                "customerValueBucket": "HIGH"
            },
            "eventName": "purchase",
            "clientId": "1234567890.1761581763",
            "userId": "user_ABC12345",
            "additionalEventParameters": [
                {
                    "parameterName": "ad_unit_name",
                    "value": "Banner_01"
                }
            ],
            "cartData": {
                "transactionDiscount": 6.66,
                "items": [
                    {
                        "itemId": "SKU_12345",
                        "quantity": 3,
                        "unitPrice": 10.01,
                        "additionalItemParameters": [
                            {
                                "parameterName": "item_name",
                                "value": "Stan and Friends Tee"
                            },
                            {
                                "parameterName": "affiliation",
                                "value": "Google Merchandise Store"
                            },
                            {
                                "parameterName": "coupon",
                                "value": "SUMMER_FUN"
                            },
                            {
                                "parameterName": "discount",
                                "value": "2.22"
                            },
                            {
                                "parameterName": "index",
                                "value": "0"
                            },
                            {
                                "parameterName": "item_brand",
                                "value": "Google"
                            },
                            {
                                "parameterName": "item_category",
                                "value": "Apparel"
                            },
                            {
                                "parameterName": "item_category2",
                                "value": "Adult"
                            },
                            {
                                "parameterName": "item_category3",
                                "value": "Shirts"
                            },
                            {
                                "parameterName": "item_category4",
                                "value": "Crew"
                            },
                            {
                                "parameterName": "item_category5",
                                "value": "Short sleeve"
                            },
                            {
                                "parameterName": "item_list_id",
                                "value": "related_products"
                            },
                            {
                                "parameterName": "item_list_name",
                                "value": "Related Products"
                            }
                        ]
                    }
                ]
            }
        },
        {
            "adIdentifiers": {
                "gclid": "GCLID_2"
            },
            "conversionValue": 42.02,
            "currency": "EUR",
            "eventTimestamp": "2025-06-11T04:42:33Z",
            "transactionId": "DEF999911111",
            "eventSource": "WEB",
            "userData": {
                "userIdentifiers": [
                    {
                        "emailAddress": "3E693CF7E5B67880BFF33B2D2626DADB7BF1D4BC737192E47CF8BAA89ACF2250"
                    },
                    {
                        "emailAddress": "223EBDA6F6889B1494551BA902D9D381DAF2F642BAE055888E96343D53E9F9C4"
                    },
                    {
                        "address": {
                            "givenName": "2752B88686847FA5C86F47B94CE652B7B3F22A91C37617D451A4DB9AFA431450",
                            "familyName": "6654977D57DDDD3C0329CA741B109EF6CD6430BEDD00008AAD213DF25683D77F",
                            "regionCode": "PT",
                            "postalCode": "1229-076"
                        }
                    }
                ]
            },
            "userProperties": {
                "customerType": "RETURNING"
            },
            "eventName": "purchase",
            "clientId": "9876543210.1761582117",
            "userId": "user_DEF9876",
            "additionalEventParameters": [
                {
                    "parameterName": "ad_unit_name",
                    "value": "Banner_02"
                }
            ],
            "cartData": {
                "transactionDiscount": 6.66,
                "items": [
                    {
                        "itemId": "SKU_12346",
                        "quantity": 2,
                        "unitPrice": 21.01,
                        "additionalItemParameters": [
                            {
                                "parameterName": "item_name",
                                "value": "Google Grey Women's Tee"
                            },
                            {
                                "parameterName": "affiliation",
                                "value": "Google Merchandise Store"
                            },
                            {
                                "parameterName": "coupon",
                                "value": "SUMMER_FUN"
                            },
                            {
                                "parameterName": "discount",
                                "value": "3.33"
                            },
                            {
                                "parameterName": "index",
                                "value": "1"
                            },
                            {
                                "parameterName": "item_brand",
                                "value": "Google"
                            },
                            {
                                "parameterName": "item_category",
                                "value": "Apparel"
                            },
                            {
                                "parameterName": "item_category2",
                                "value": "Adult"
                            },
                            {
                                "parameterName": "item_category3",
                                "value": "Shirts"
                            },
                            {
                                "parameterName": "item_category4",
                                "value": "Crew"
                            },
                            {
                                "parameterName": "item_category5",
                                "value": "Short sleeve"
                            },
                            {
                                "parameterName": "item_list_id",
                                "value": "related_products"
                            },
                            {
                                "parameterName": "item_list_name",
                                "value": "Related Products"
                            }
                        ]
                    }
                ]
            }
        }
    ],
    "validateOnly": true
}

.NET

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System.Text.Json;
using CommandLine;
using Google.Ads.DataManager.Util;
using Google.Ads.DataManager.V1;
using Google.Protobuf.WellKnownTypes;
using static Google.Ads.DataManager.V1.ProductAccount.Types;

namespace Google.Ads.DataManager.Samples
{
    // <summary>
    // Sends an <see cref="IngestEventsRequest" /> without using encryption.
    //
    // Event data is read from a data file. See the <c>events_1.json</c> file in the
    // <c>sampledata</c> directory for an example.
    // </summary>
    public class IngestEvents
    {
        private static readonly int MaxEventsPerRequest = 2_000;

        [Verb("ingest-events", HelpText = "Sends an IngestEventsRequest without using encryption.")]
        public class Options
        {
            [Option(
                "operatingAccountType",
                Required = true,
                HelpText = "Account type of the operating account"
            )]
            public AccountType OperatingAccountType { get; set; }

            [Option(
                "operatingAccountId",
                Required = true,
                HelpText = "ID of the operating account"
            )]
            public string OperatingAccountId { get; set; } = null!;

            [Option(
                "loginAccountType",
                Required = false,
                HelpText = "Account type of the login account"
            )]
            public AccountType? LoginAccountType { get; set; }

            [Option("loginAccountId", Required = false, HelpText = "ID of the login account")]
            public string? LoginAccountId { get; set; }

            [Option(
                "linkedAccountProduct",
                Required = false,
                HelpText = "Account type of the linked account"
            )]
            public AccountType? LinkedAccountType { get; set; }

            [Option("linkedAccountId", Required = false, HelpText = "ID of the linked account")]
            public string? LinkedAccountId { get; set; }

            [Option(
                "conversionActionId",
                Required = true,
                HelpText = "ID of the conversion action"
            )]
            public string ConversionActionId { get; set; } = null!;

            [Option(
                "jsonFile",
                Required = true,
                HelpText = "JSON file containing user data to ingest"
            )]
            public string JsonFile { get; set; } = null!;

            [Option(
                "validateOnly",
                Default = true,
                HelpText = "Whether to enable validateOnly on the request"
            )]
            public bool ValidateOnly { get; set; }
        }

        public void Run(Options options)
        {
            RunExample(
                options.OperatingAccountType,
                options.OperatingAccountId,
                options.LoginAccountType,
                options.LoginAccountId,
                options.LinkedAccountType,
                options.LinkedAccountId,
                options.ConversionActionId,
                options.JsonFile,
                options.ValidateOnly
            );
        }

        private void RunExample(
            AccountType operatingAccountType,
            string operatingAccountId,
            AccountType? loginAccountType,
            string? loginAccountId,
            AccountType? linkedAccountType,
            string? linkedAccountId,
            string conversionActionId,
            string jsonFile,
            bool validateOnly
        )
        {
            if (loginAccountId == null ^ loginAccountType == null)
            {
                throw new ArgumentException(
                    "Must specify either both or neither of login account ID and login account "
                        + "type"
                );
            }
            if (linkedAccountId == null ^ linkedAccountType == null)
            {
                throw new ArgumentException(
                    "Must specify either both or neither of linked account ID and linked account "
                        + "type"
                );
            }

            // Reads member data from the data file.
            List<EventRecord> eventRecords = ReadEventData(jsonFile);
            // Gets an instance of the UserDataFormatter for normalizing and formatting the data.
            UserDataFormatter userDataFormatter = new UserDataFormatter();

            // Builds the events collection for the request.
            var events = new List<Event>();
            foreach (var eventRecord in eventRecords)
            {
                var eventBuilder = new Event();

                try
                {
                    eventBuilder.EventTimestamp = Timestamp.FromDateTime(
                        DateTime.Parse(eventRecord.Timestamp ?? "").ToUniversalTime()
                    );
                }
                catch (FormatException)
                {
                    Console.WriteLine(
                        $"Skipping event with invalid timestamp: {eventRecord.Timestamp}"
                    );
                    continue;
                }

                if (string.IsNullOrEmpty(eventRecord.TransactionId))
                {
                    Console.WriteLine("Skipping event with no transaction ID");
                    continue;
                }
                eventBuilder.TransactionId = eventRecord.TransactionId;

                if (!string.IsNullOrEmpty(eventRecord.EventSource))
                {
                    if (
                        System.Enum.TryParse(
                            eventRecord.EventSource,
                            true,
                            out EventSource eventSource
                        )
                    )
                    {
                        eventBuilder.EventSource = eventSource;
                    }
                    else
                    {
                        Console.WriteLine(
                            $"Skipping event with invalid event source: {eventRecord.EventSource}"
                        );
                        continue;
                    }
                }

                if (!string.IsNullOrEmpty(eventRecord.Gclid))
                {
                    eventBuilder.AdIdentifiers = new AdIdentifiers { Gclid = eventRecord.Gclid };
                }

                if (!string.IsNullOrEmpty(eventRecord.Currency))
                {
                    eventBuilder.Currency = eventRecord.Currency;
                }

                if (eventRecord.Value.HasValue)
                {
                    eventBuilder.ConversionValue = eventRecord.Value.Value;
                }

                var userDataBuilder = new UserData();

                // Adds a UserIdentifier for each valid email address for the eventRecord.
                if (eventRecord.Emails != null)
                {
                    foreach (var email in eventRecord.Emails)
                    {
                        try
                        {
                            string preparedEmail = userDataFormatter.ProcessEmailAddress(
                                email,
                                UserDataFormatter.Encoding.Hex
                            );
                            // Adds an email address identifier with the encoded email hash.
                            userDataBuilder.UserIdentifiers.Add(
                                new UserIdentifier { EmailAddress = preparedEmail }
                            );
                        }
                        catch (ArgumentException)
                        {
                            // Skips invalid input.
                            continue;
                        }
                    }
                }

                // Adds a UserIdentifier for each valid phone number for the eventRecord.
                if (eventRecord.PhoneNumbers != null)
                {
                    foreach (var phoneNumber in eventRecord.PhoneNumbers)
                    {
                        try
                        {
                            string preparedPhoneNumber = userDataFormatter.ProcessPhoneNumber(
                                phoneNumber,
                                UserDataFormatter.Encoding.Hex
                            );
                            // Adds a phone number identifier with the encoded phone hash.
                            userDataBuilder.UserIdentifiers.Add(
                                new UserIdentifier { PhoneNumber = preparedPhoneNumber }
                            );
                        }
                        catch (ArgumentException)
                        {
                            // Skips invalid input.
                            continue;
                        }
                    }
                }

                if (userDataBuilder.UserIdentifiers.Any())
                {
                    eventBuilder.UserData = userDataBuilder;
                }
                events.Add(eventBuilder);
            }

            // Builds the Destination for the request.
            var destinationBuilder = new Destination
            {
                OperatingAccount = new ProductAccount
                {
                    AccountType = operatingAccountType,
                    AccountId = operatingAccountId,
                },
                ProductDestinationId = conversionActionId,
            };

            if (loginAccountType.HasValue && loginAccountId != null)
            {
                destinationBuilder.LoginAccount = new ProductAccount
                {
                    AccountType = loginAccountType.Value,
                    AccountId = loginAccountId,
                };
            }

            if (linkedAccountType.HasValue && linkedAccountId != null)
            {
                destinationBuilder.LinkedAccount = new ProductAccount
                {
                    AccountType = linkedAccountType.Value,
                    AccountId = linkedAccountId,
                };
            }

            IngestionServiceClient ingestionServiceClient = IngestionServiceClient.Create();

            int requestCount = 0;

            // Batches requests to send up to the maximum number of events per request.
            for (var i = 0; i < events.Count; i += MaxEventsPerRequest)
            {
                IEnumerable<Event> batch = events.Skip(i).Take(MaxEventsPerRequest);
                requestCount++;
                var request = new IngestEventsRequest
                {
                    Destinations = { destinationBuilder },
                    // Adds events from the current batch.
                    Events = { batch },
                    Consent = new Consent
                    {
                        AdPersonalization = ConsentStatus.ConsentGranted,
                        AdUserData = ConsentStatus.ConsentGranted,
                    },
                    // Sets validate_only. If true, then the Data Manager API only validates the
                    // request but doesn't apply changes.
                    ValidateOnly = validateOnly,
                    Encoding = V1.Encoding.Hex,
                };

                // Sends the data to the Data Manager API.
                IngestEventsResponse response = ingestionServiceClient.IngestEvents(request);
                Console.WriteLine($"Response for request #{requestCount}:\n{response}");
            }
            Console.WriteLine($"# of requests sent: {requestCount}");
        }

        private class EventRecord
        {
            public List<string>? Emails { get; set; }
            public List<string>? PhoneNumbers { get; set; }
            public string? Timestamp { get; set; }
            public string? TransactionId { get; set; }
            public string? EventSource { get; set; }
            public double? Value { get; set; }
            public string? Currency { get; set; }
            public string? Gclid { get; set; }
        }

        private List<EventRecord> ReadEventData(string jsonFile)
        {
            string jsonString = File.ReadAllText(jsonFile);
            var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
            return JsonSerializer.Deserialize<List<EventRecord>>(jsonString, options)
                ?? new List<EventRecord>();
        }
    }
}

Java

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.ads.datamanager.samples;

import com.beust.jcommander.Parameter;
import com.google.ads.datamanager.samples.common.BaseParamsConfig;
import com.google.ads.datamanager.util.UserDataFormatter;
import com.google.ads.datamanager.util.UserDataFormatter.Encoding;
import com.google.ads.datamanager.v1.AdIdentifiers;
import com.google.ads.datamanager.v1.Consent;
import com.google.ads.datamanager.v1.ConsentStatus;
import com.google.ads.datamanager.v1.Destination;
import com.google.ads.datamanager.v1.Event;
import com.google.ads.datamanager.v1.EventSource;
import com.google.ads.datamanager.v1.IngestEventsRequest;
import com.google.ads.datamanager.v1.IngestEventsResponse;
import com.google.ads.datamanager.v1.IngestionServiceClient;
import com.google.ads.datamanager.v1.ProductAccount;
import com.google.ads.datamanager.v1.ProductAccount.AccountType;
import com.google.ads.datamanager.v1.UserData;
import com.google.ads.datamanager.v1.UserIdentifier;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.reflect.TypeToken;
import com.google.gson.GsonBuilder;
import com.google.protobuf.util.Timestamps;
import java.io.BufferedReader;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

/**
 * Sends an {@link IngestEventsRequest} without using encryption.
 *
 * <p>Event data is read from a data file. See the {@code events_1.json} file in the {@code
 * resources/sampledata} directory for a sample file.
 */
public class IngestEvents {
  private static final Logger LOGGER = Logger.getLogger(IngestEvents.class.getName());

  /** The maximum number of events allowed per request. */
  private static final int MAX_EVENTS_PER_REQUEST = 2_000;

  private static final class ParamsConfig extends BaseParamsConfig<ParamsConfig> {

    @Parameter(
        names = "--operatingAccountType",
        required = true,
        description = "Account type of the operating account")
    AccountType operatingAccountType;

    @Parameter(
        names = "--operatingAccountId",
        required = true,
        description = "ID of the operating account")
    String operatingAccountId;

    @Parameter(
        names = "--loginAccountType",
        required = false,
        description = "Account type of the login account")
    AccountType loginAccountType;

    @Parameter(
        names = "--loginAccountId",
        required = false,
        description = "ID of the login account")
    String loginAccountId;

    @Parameter(
        names = "--linkedAccountType",
        required = false,
        description = "Account type of the linked account")
    AccountType linkedAccountType;

    @Parameter(
        names = "--linkedAccountId",
        required = false,
        description = "ID of the linked account")
    String linkedAccountId;

    @Parameter(
        names = "--conversionActionId",
        required = true,
        description = "ID of the conversion action")
    String conversionActionId;

    @Parameter(
        names = "--jsonFile",
        required = true,
        description = "JSON file containing user data to ingest")
    String jsonFile;

    @Parameter(
        names = "--validateOnly",
        required = false,
        arity = 1,
        description = "Whether to enable validateOnly on the request")
    boolean validateOnly = true;
  }

  public static void main(String[] args) throws IOException {
    ParamsConfig paramsConfig = new ParamsConfig().parseOrExit(args);
    if ((paramsConfig.loginAccountId == null) != (paramsConfig.loginAccountType == null)) {
      throw new IllegalArgumentException(
          "Must specify either both or neither of login account ID and login account type");
    }
    if ((paramsConfig.linkedAccountId == null) != (paramsConfig.linkedAccountType == null)) {
      throw new IllegalArgumentException(
          "Must specify either both or neither of linked account ID and linked account type");
    }
    new IngestEvents().runExample(paramsConfig);
  }

  /**
   * Runs the example. This sample assumes that the login and operating account are the same.
   *
   * @param params the parameters for the example
   */
  private void runExample(ParamsConfig params) throws IOException {
    // Reads event data from the JSON file.
    List<EventRecord> eventRecords = readEventData(params.jsonFile);

    // Gets an instance of the UserDataFormatter for normalizing and formatting the data.
    UserDataFormatter userDataFormatter = UserDataFormatter.create();

    // Builds the events collection for the request.
    List<Event> events = new ArrayList<>();
    for (EventRecord eventRecord : eventRecords) {
      Event.Builder eventBuilder = Event.newBuilder();
      try {
        eventBuilder.setEventTimestamp(Timestamps.parse(eventRecord.timestamp));
      } catch (ParseException pe) {
        LOGGER.warning(
            () ->
                String.format("Skipping event with invalid timestamp: %s", eventRecord.timestamp));
        continue;
      }

      if (Strings.isNullOrEmpty(eventRecord.transactionId)) {
        LOGGER.warning("Skipping event with no transaction ID");
        continue;
      }
      eventBuilder.setTransactionId(eventRecord.transactionId);
      if (!Strings.isNullOrEmpty(eventRecord.eventSource)) {
        try {
          eventBuilder.setEventSource(EventSource.valueOf(eventRecord.eventSource));
        } catch (IllegalArgumentException iae) {
          LOGGER.warning("Skipping event with invalid event source: " + eventRecord.eventSource);
          continue;
        }
      }

      if (!Strings.isNullOrEmpty(eventRecord.gclid)) {
        eventBuilder.setAdIdentifiers(AdIdentifiers.newBuilder().setGclid(eventRecord.gclid));
      }

      if (!Strings.isNullOrEmpty(eventRecord.currency)) {
        eventBuilder.setCurrency(eventRecord.currency);
      }

      if (eventRecord.value != null) {
        eventBuilder.setConversionValue(eventRecord.value);
      }

      UserData.Builder userDataBuilder = UserData.newBuilder();

      // Adds a UserIdentifier for each valid email address for the eventRecord.
      if (eventRecord.emails != null) {
        for (String email : eventRecord.emails) {
          String preparedEmail;
          try {
            preparedEmail = userDataFormatter.processEmailAddress(email, Encoding.HEX);
          } catch (IllegalArgumentException iae) {
            // Skips invalid input.
            continue;
          }
          // Sets the email address identifier to the encoded email hash.
          userDataBuilder.addUserIdentifiers(
              UserIdentifier.newBuilder().setEmailAddress(preparedEmail));
        }
      }

      // Adds a UserIdentifier for each valid phone number for the eventRecord.
      if (eventRecord.phoneNumbers != null) {
        for (String phoneNumber : eventRecord.phoneNumbers) {
          String preparedPhoneNumber;
          try {
            preparedPhoneNumber = userDataFormatter.processPhoneNumber(phoneNumber, Encoding.HEX);
          } catch (IllegalArgumentException iae) {
            // Skips invalid input.
            continue;
          }
          // Sets the phone number identifier to the encoded phone number hash.
          userDataBuilder.addUserIdentifiers(
              UserIdentifier.newBuilder().setPhoneNumber(preparedPhoneNumber));
        }
      }

      if (userDataBuilder.getUserIdentifiersCount() > 0) {
        eventBuilder.setUserData(userDataBuilder);
      }
      events.add(eventBuilder.build());
    }

    // Builds the Destination for the request.
    Destination.Builder destinationBuilder =
        Destination.newBuilder()
            .setOperatingAccount(
                ProductAccount.newBuilder()
                    .setAccountType(params.operatingAccountType)
                    .setAccountId(params.operatingAccountId))
            .setProductDestinationId(params.conversionActionId);
    if (params.loginAccountType != null && params.loginAccountId != null) {
      destinationBuilder.setLoginAccount(
          ProductAccount.newBuilder()
              .setAccountType(params.loginAccountType)
              .setAccountId(params.loginAccountId));
    }
    if (params.linkedAccountType != null && params.linkedAccountId != null) {
      destinationBuilder.setLinkedAccount(
          ProductAccount.newBuilder()
              .setAccountType(params.linkedAccountType)
              .setAccountId(params.linkedAccountId));
    }

    try (IngestionServiceClient ingestionServiceClient = IngestionServiceClient.create()) {
      int requestCount = 0;
      // Batches requests to send up to the maximum number of events per request.
      for (List<Event> eventsBatch : Lists.partition(events, MAX_EVENTS_PER_REQUEST)) {
        requestCount++;
        // Builds the request.
        IngestEventsRequest request =
            IngestEventsRequest.newBuilder()
                .addDestinations(destinationBuilder)
                // Adds events from the current batch.
                .addAllEvents(eventsBatch)
                .setConsent(
                    Consent.newBuilder()
                        .setAdPersonalization(ConsentStatus.CONSENT_GRANTED)
                        .setAdUserData(ConsentStatus.CONSENT_GRANTED))
                // Sets validate_only. If true, then the Data Manager API only validates the request
                // but doesn't apply changes.
                .setValidateOnly(params.validateOnly)
                // Sets encoding to match the encoding used.
                .setEncoding(com.google.ads.datamanager.v1.Encoding.HEX)
                .build();

        LOGGER.info(() -> String.format("Request:%n%s", request));
        IngestEventsResponse response = ingestionServiceClient.ingestEvents(request);
        LOGGER.info(String.format("Response for request #:%n%s", requestCount, response));
      }

      LOGGER.info("# of requests sent: " + requestCount);
    }
  }

  /** Data object for a single row of input data. */
  @SuppressWarnings("unused")
  private static class EventRecord {
    private List<String> emails;
    private List<String> phoneNumbers;
    private String timestamp;
    private String transactionId;
    private String eventSource;
    private Double value;
    private String currency;
    private String gclid;
  }

  /** Reads the data file and parses each line into a {@link EventRecord} object. */
  private List<EventRecord> readEventData(String jsonFile) throws IOException {
    try (BufferedReader jsonReader =
        Files.newBufferedReader(Paths.get(jsonFile), StandardCharsets.UTF_8)) {
      // Define the type for Gson to deserialize into (List of EventRecord objects)
      Type recordListType = new TypeToken<ArrayList<EventRecord>>() {}.getType();

      // Parse the JSON string from the file into a List of EventRecord objects
      return new GsonBuilder().create().fromJson(jsonReader, recordListType);
    }
  }
}

โหนด

#!/usr/bin/env node
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

import {IngestionServiceClient} from '@google-ads/datamanager';
import {protos} from '@google-ads/datamanager';
const {
  Event: DataManagerEvent,
  Destination,
  Encoding: DataManagerEncoding,
  EventSource,
  Consent,
  ConsentStatus,
  IngestEventsRequest,
  ProductAccount,
  UserData,
  UserIdentifier,
} = protos.google.ads.datamanager.v1;
import {UserDataFormatter, Encoding} from '@google-ads/data-manager-util';
import * as fs from 'fs';
import * as yargs from 'yargs';

const MAX_EVENTS_PER_REQUEST = 10000;

interface Arguments {
  operating_account_type: string;
  operating_account_id: string;
  conversion_action_id: string;
  json_file: string;
  validate_only: boolean;
  login_account_type?: string;
  login_account_id?: string;
  linked_account_type?: string;
  linked_account_id?: string;
  [x: string]: unknown;
}

interface EventRow {
  timestamp: string;
  transactionId: string;
  eventSource?: string;
  gclid?: string;
  currency?: string;
  value?: number;
  emails?: string[];
  phoneNumbers?: string[];
}

/**
 * The main function for the IngestEvents sample.
 */
async function main() {
  const argv: Arguments = yargs
    .option('operating_account_type', {
      describe: 'The account type of the operating account.',
      type: 'string',
      required: true,
    })
    .option('operating_account_id', {
      describe: 'The ID of the operating account.',
      type: 'string',
      required: true,
    })
    .option('conversion_action_id', {
      describe: 'The ID of the conversion action.',
      type: 'string',
      required: true,
    })
    .option('json_file', {
      describe: 'JSON file containing user data to ingest.',
      type: 'string',
      required: true,
    })
    .option('validate_only', {
      describe: 'Whether to enable validate_only on the request.',
      type: 'boolean',
      default: true,
    })
    .option('login_account_type', {
      describe: 'The account type of the login account.',
      type: 'string',
    })
    .option('login_account_id', {
      describe: 'The ID of the login account.',
      type: 'string',
    })
    .option('linked_account_type', {
      describe: 'The account type of the linked account.',
      type: 'string',
    })
    .option('linked_account_id', {
      describe: 'The ID of the linked account.',
      type: 'string',
    })
    .option('config', {
      describe: 'Path to a JSON file with arguments.',
      type: 'string',
    })
    .config('config')
    .check((args: Arguments) => {
      if (
        (args.login_account_type && !args.login_account_id) ||
        (!args.login_account_type && args.login_account_id)
      ) {
        throw new Error(
          'Must specify either both or neither of login account type ' +
            'and login account ID',
        );
      }
      if (
        (args.linked_account_type && !args.linked_account_id) ||
        (!args.linked_account_type && args.linked_account_id)
      ) {
        throw new Error(
          'Must specify either both or neither of linked account type ' +
            'and linked account ID',
        );
      }
      return true;
    })
    .parseSync();

  // Reads event data from the JSON file.
  const eventRows: EventRow[] = readEventDataFile(argv.json_file);

  // Builds the events collection for the request.
  const events = [];
  const formatter = new UserDataFormatter();
  for (const eventRow of eventRows) {
    const event = DataManagerEvent.create();
    try {
      const date = new Date(eventRow.timestamp);
      event.eventTimestamp = {
        seconds: Math.floor(date.getTime() / 1000),
        nanos: (date.getTime() % 1000) * 1e6,
      };
    } catch (e) {
      console.warn(
        `Invalid timestamp format: ${eventRow.timestamp}. Skipping row.`,
      );
      continue;
    }

    if (!eventRow.transactionId) {
      console.warn('Skipping event with no transaction ID');
      continue;
    }
    event.transactionId = eventRow.transactionId;

    if (eventRow.eventSource) {
      const eventSourceEnumValue: number | undefined =
        EventSource[eventRow.eventSource as keyof typeof EventSource];
      if (eventSourceEnumValue === undefined) {
        console.warn(
          `Skipping event with invalid event_source: ${eventRow.eventSource}`,
        );
        continue;
      }
      event.eventSource = eventSourceEnumValue;
    }

    if (eventRow.gclid) {
      event.adIdentifiers = {gclid: eventRow.gclid};
    }

    if (eventRow.currency) {
      event.currency = eventRow.currency;
    }

    if (eventRow.value) {
      event.conversionValue = eventRow.value;
    }

    const userData = UserData.create();
    // Adds a UserIdentifier for each valid email address for the eventRecord.
    if (eventRow.emails) {
      for (const email of eventRow.emails) {
        try {
          const processedEmail = formatter.processEmailAddress(
            email,
            Encoding.HEX,
          );
          userData.userIdentifiers.push(
            UserIdentifier.create({emailAddress: processedEmail}),
          );
        } catch (e) {
          console.warn(`Invalid email address: ${email}. Skipping.`);
        }
      }
    }

    // Adds a UserIdentifier for each valid phone number for the eventRecord.
    if (eventRow.phoneNumbers) {
      for (const phoneNumber of eventRow.phoneNumbers) {
        try {
          const processedPhone = formatter.processPhoneNumber(
            phoneNumber,
            Encoding.HEX,
          );
          userData.userIdentifiers.push(
            UserIdentifier.create({phoneNumber: processedPhone}),
          );
        } catch (e) {
          console.warn(`Invalid phone: ${phoneNumber}. Skipping.`);
        }
      }
    }

    if (userData.userIdentifiers.length > 0) {
      event.userData = userData;
    }

    events.push(event);
  }

  // Sets up the Destination.
  const operatingAccountType = convertToAccountType(
    argv.operating_account_type,
    'operating_account_type',
  );

  const destination = Destination.create({
    operatingAccount: ProductAccount.create({
      accountType: operatingAccountType,
      accountId: argv.operating_account_id,
    }),
    productDestinationId: argv.conversion_action_id,
  });

  // The login account is optional.
  if (argv.login_account_type) {
    const loginAccountType = convertToAccountType(
      argv.login_account_type,
      'login_account_type',
    );
    destination.loginAccount = ProductAccount.create({
      accountType: loginAccountType,
      accountId: argv.login_account_id,
    });
  }

  // The linked account is optional.
  if (argv.linked_account_type) {
    const linkedAccountType = convertToAccountType(
      argv.linked_account_type,
      'linked_account_type',
    );
    destination.linkedAccount = ProductAccount.create({
      accountType: linkedAccountType,
      accountId: argv.linked_account_id,
    });
  }

  const client = new IngestionServiceClient();

  let requestCount = 0;
  // Batches requests to send up to the maximum number of events per request.
  for (let i = 0; i < events.length; i += MAX_EVENTS_PER_REQUEST) {
    requestCount++;
    const eventsBatch = events.slice(i, i + MAX_EVENTS_PER_REQUEST);

    // Builds the request.
    const request = IngestEventsRequest.create({
      destinations: [destination],
      // Adds events from the current batch.
      events: eventsBatch,
      consent: Consent.create({
        adUserData: ConsentStatus.CONSENT_GRANTED,
        adPersonalization: ConsentStatus.CONSENT_GRANTED,
      }),
      // Sets encoding to match the encoding used.
      encoding: DataManagerEncoding.HEX,
      // Sets validate_only. If true, then the Data Manager API only validates the request
      validateOnly: argv.validate_only,
    });

    const [response] = await client.ingestEvents(request);
    console.log(`Response for request #${requestCount}:\n`, response);
  }
  console.log(`# of requests sent: ${requestCount}`);
}

/**
 * Reads the event data from the given JSON file.
 * @param {string} jsonFile The path to the JSON file.
 * @return {EventRow[]} An array of event data.
 */
function readEventDataFile(jsonFile: string): EventRow[] {
  const fileContent = fs.readFileSync(jsonFile, 'utf8');
  return JSON.parse(fileContent);
}

/**
 * Validates that a given string is an enum value for the AccountType enum, and
 * if validation passes, returns the AccountType enum value.
 * @param proposedValue the name of an AccountType enum value
 * @param paramName the name of the parameter to use in the error message if validation fails
 * @returns {protos.google.ads.datamanager.v1.ProductAccount.AccountType} The corresponding enum value.
 * @throws {Error} If the string is not an AccountType enum value.
 */
function convertToAccountType(
  proposedValue: string,
  paramName: string,
): protos.google.ads.datamanager.v1.ProductAccount.AccountType {
  const AccountType = ProductAccount.AccountType;
  const accountTypeEnumNames = Object.keys(AccountType).filter(key =>
    isNaN(Number(key)),
  );
  if (!accountTypeEnumNames.includes(proposedValue)) {
    throw new Error(`Invalid ${paramName}: ${proposedValue}`);
  }
  return AccountType[proposedValue as keyof typeof AccountType];
}

if (require.main === module) {
  main().catch(console.error);
}

PHP

<?php
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * Sample of sending an IngestEventsRequest without encryption.
 */

require_once dirname(__DIR__, 1) . '/vendor/autoload.php';

use Google\Ads\DataManager\V1\AdIdentifiers;
use Google\Ads\DataManager\V1\Client\IngestionServiceClient;
use Google\Ads\DataManager\V1\Consent;
use Google\Ads\DataManager\V1\ConsentStatus;
use Google\Ads\DataManager\V1\Destination;
use Google\Ads\DataManager\V1\Encoding as DataManagerEncoding;
use Google\Ads\DataManager\V1\Event;
use Google\Ads\DataManager\V1\EventSource;
use Google\Ads\DataManager\V1\IngestEventsRequest;
use Google\Ads\DataManager\V1\ProductAccount;
use Google\Ads\DataManager\V1\ProductAccount\AccountType;
use Google\Ads\DataManager\V1\UserData;
use Google\Ads\DataManager\V1\UserIdentifier;
use Google\Ads\DataManagerUtil\Encoding;
use Google\Ads\DataManagerUtil\Formatter;
use Google\ApiCore\ApiException;
use Google\Protobuf\Timestamp;

// The maximum number of events allowed per request.
const MAX_EVENTS_PER_REQUEST = 2000;

/**
 * Reads the JSON-formatted event data file.
 *
 * @param string $jsonFile The event data file.
 * @return array A list of associative arrays, each representing an event.
 */
function readEventDataFile(string $jsonFile): array
{
    $jsonContent = file_get_contents($jsonFile);
    if ($jsonContent === false) {
        throw new \RuntimeException(sprintf('Could not read JSON file: %s', $jsonFile));
    }
    $events = json_decode($jsonContent, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        throw new \RuntimeException(sprintf('Invalid JSON in file: %s', $jsonFile));
    }
    return $events;
}

/**
 * Runs the sample.
 *
 * @param int $operatingAccountType The account type of the operating account.
 * @param string $operatingAccountId The ID of the operating account.
 * @param string $conversionActionId The ID of the conversion action.
 * @param string $jsonFile The JSON file containing event data.
 * @param bool $validateOnly Whether to enable validateOnly on the request.
 * @param int|null $loginAccountType The account type of the login account.
 * @param string|null $loginAccountId The ID of the login account.
 * @param int|null $linkedAccountType The account type of the linked account.
 * @param string|null $linkedAccountId The ID of the linked account.
 */
function main(
    int $operatingAccountType,
    string $operatingAccountId,
    string $conversionActionId,
    string $jsonFile,
    bool $validateOnly,
    ?int $loginAccountType = null,
    ?string $loginAccountId = null,
    ?int $linkedAccountType = null,
    ?string $linkedAccountId = null
): void {
    // Reads event data from the data file.
    $eventRecords = readEventDataFile($jsonFile);

    // Gets an instance of the UserDataFormatter for normalizing and formatting the data.
    $formatter = new Formatter();

    // Builds the events collection for the request.
    $events = [];
    foreach ($eventRecords as $eventRecord) {
        $event = new Event();

        if (empty($eventRecord['timestamp'])) {
            error_log('Skipping event with no timestamp.');
            continue;
        }
        try {
            $dateTime = new DateTime($eventRecord['timestamp']);
            $timestamp = new Timestamp();
            $timestamp->fromDateTime($dateTime);
            $event->setEventTimestamp($timestamp);
        } catch (\Exception $e) {
            error_log(sprintf('Skipping event with invalid timestamp: %s', $eventRecord['timestamp']));
            continue;
        }

        if (empty($eventRecord['transactionId'])) {
            error_log('Skipping event with no transaction ID');
            continue;
        }
        $event->setTransactionId($eventRecord['transactionId']);

        if (!empty($eventRecord['eventSource'])) {
            try {
                $event->setEventSource(EventSource::value($eventRecord['eventSource']));
            } catch (\UnexpectedValueException $e) {
                error_log('Skipping event with invalid event source: ' . $eventRecord['eventSource']);
                continue;
            }
        }

        if (!empty($eventRecord['gclid'])) {
            $event->setAdIdentifiers((new AdIdentifiers())->setGclid($eventRecord['gclid']));
        }

        if (!empty($eventRecord['currency'])) {
            $event->setCurrency($eventRecord['currency']);
        }

        if (isset($eventRecord['value'])) {
            $event->setConversionValue($eventRecord['value']);
        }

        $userData = new UserData();
        $identifiers = [];

        if (!empty($eventRecord['emails'])) {
            foreach ($eventRecord['emails'] as $email) {
                try {
                    $preparedEmail = $formatter->processEmailAddress($email, Encoding::Hex);
                    $identifiers[] = (new UserIdentifier())->setEmailAddress($preparedEmail);
                } catch (\InvalidArgumentException $e) {
                    // Skips invalid input.
                    error_log(sprintf('Skipping invalid email: %s', $e->getMessage()));
                    continue;
                }
            }
        }

        if (!empty($eventRecord['phoneNumbers'])) {
            foreach ($eventRecord['phoneNumbers'] as $phoneNumber) {
                try {
                    $preparedPhoneNumber = $formatter->processPhoneNumber($phoneNumber, Encoding::Hex);
                    $identifiers[] = (new UserIdentifier())->setPhoneNumber($preparedPhoneNumber);
                } catch (\InvalidArgumentException $e) {
                    // Skips invalid input.
                    error_log(sprintf('Skipping invalid phone number: %s', $e->getMessage()));
                    continue;
                }
            }
        }

        if (!empty($identifiers)) {
            $userData->setUserIdentifiers($identifiers);
            $event->setUserData($userData);
        }
        $events[] = $event;
    }

    // Builds the destination for the request.
    $destination = (new Destination())
        ->setOperatingAccount((new ProductAccount())
            ->setAccountType($operatingAccountType)
            ->setAccountId($operatingAccountId))
        ->setProductDestinationId($conversionActionId);

    if ($loginAccountType !== null && $loginAccountId !== null) {
        $destination->setLoginAccount((new ProductAccount())
            ->setAccountType($loginAccountType)
            ->setAccountId($loginAccountId));
    }

    if ($linkedAccountType !== null && $linkedAccountId !== null) {
        $destination->setLinkedAccount((new ProductAccount())
            ->setAccountType($linkedAccountType)
            ->setAccountId($linkedAccountId));
    }

    $client = new IngestionServiceClient();
    try {
        $requestCount = 0;
        // Batches requests to send up to the maximum number of events per request.
        foreach (array_chunk($events, MAX_EVENTS_PER_REQUEST) as $eventsBatch) {
            $requestCount++;
            // Builds the request.
            $request = (new IngestEventsRequest())
                ->setDestinations([$destination])
                ->setEvents($eventsBatch)
                ->setConsent((new Consent())
                    ->setAdUserData(ConsentStatus::CONSENT_GRANTED)
                    ->setAdPersonalization(ConsentStatus::CONSENT_GRANTED)
                )
                ->setValidateOnly($validateOnly)
                ->setEncoding(DataManagerEncoding::HEX);

            echo "Request:\n" . json_encode(json_decode($request->serializeToJsonString()), JSON_PRETTY_PRINT) . "\n";
            $response = $client->ingestEvents($request);
            echo "Response for request #{$requestCount}:\n" . json_encode(json_decode($response->serializeToJsonString()), JSON_PRETTY_PRINT) . "\n";
        }
        echo "# of requests sent: {$requestCount}\n";
    } catch (ApiException $e) {
        echo 'Error sending request: ' . $e->getMessage() . "\n";
    } finally {
        $client->close();
    }
}

// Command-line argument parsing
$options = getopt(
    '',
    [
        'operating_account_type:',
        'operating_account_id:',
        'login_account_type::',
        'login_account_id::',
        'linked_account_type::',
        'linked_account_id::',
        'conversion_action_id:',
        'json_file:',
        'validate_only::'
    ]
);

$operatingAccountType = $options['operating_account_type'] ?? null;
$operatingAccountId = $options['operating_account_id'] ?? null;
$conversionActionId = $options['conversion_action_id'] ?? null;
$jsonFile = $options['json_file'] ?? null;

// Only validates requests by default.
$validateOnly = true;
if (array_key_exists('validate_only', $options)) {
    $value = $options['validate_only'];
    // `getopt` with `::` returns boolean `false` if the option is passed without a value.
    if ($value === false || !in_array($value, ['true', 'false'], true)) {
        echo "Error: --validate_only requires a value of 'true' or 'false'.\n";
        exit(1);
    }
    $validateOnly = ($value === 'true');
}

if (empty($operatingAccountType) || empty($operatingAccountId) || empty($conversionActionId) || empty($jsonFile)) {
    echo 'Usage: php ingest_events.php ' .
        '--operating_account_type=<account_type> ' .
        '--operating_account_id=<account_id> ' .
        '--conversion_action_id=<conversion_action_id> ' .
        "--json_file=<path_to_json>\n" .
        'Optional: --login_account_type=<account_type> --login_account_id=<account_id> ' .
        '--linked_account_type=<account_type> --linked_account_id=<account_id> ' .
        "--validate_only=<true|false>\n";
    exit(1);
}

// Converts the operating account type string to an AccountType enum.
$parsedOperatingAccountType = AccountType::value($operatingAccountType);

if (isset($options['login_account_type']) != isset($options['login_account_id'])) {
    throw new \InvalidArgumentException(
        'Must specify either both or neither of login account type and login account ID'
    );
}

$parsedLoginAccountType = null;
if (isset($options['login_account_type'])) {
    // Converts the login account type string to an AccountType enum.
    $parsedLoginAccountType = AccountType::value($options['login_account_type']);
}

if (isset($options['linked_account_type']) != isset($options['linked_account_id'])) {
    throw new \InvalidArgumentException(
        'Must specify either both or neither of linked account type and linked account ID'
    );
}

$parsedLinkedAccountType = null;
if (isset($options['linked_account_type'])) {
    // Converts the linked account type string to an AccountType enum.
    $parsedLinkedAccountType = AccountType::value($options['linked_account_type']);
}

main(
    $parsedOperatingAccountType,
    $operatingAccountId,
    $conversionActionId,
    $jsonFile,
    $validateOnly,
    $parsedLoginAccountType,
    $options['login_account_id'] ?? null,
    $parsedLinkedAccountType,
    $options['linked_account_id'] ?? null
);

Python

#!/usr/bin/env python
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Sample of sending an IngestEventsRequest without encryption."""


import argparse
import json
import logging
from typing import Any, Dict, List, Optional

from google.ads import datamanager_v1
from google.ads.datamanager_util import Formatter
from google.ads.datamanager_util.format import Encoding
from google.protobuf.timestamp_pb2 import Timestamp

_logger = logging.getLogger(__name__)

# The maximum number of events allowed per request.
_MAX_EVENTS_PER_REQUEST = 10_000


def main(
    operating_account_type: datamanager_v1.ProductAccount.AccountType,
    operating_account_id: str,
    conversion_action_id: str,
    json_file: str,
    validate_only: bool,
    login_account_type: Optional[
        datamanager_v1.ProductAccount.AccountType
    ] = None,
    login_account_id: Optional[str] = None,
    linked_account_type: Optional[
        datamanager_v1.ProductAccount.AccountType
    ] = None,
    linked_account_id: Optional[str] = None,
) -> None:
    """Runs the sample.
    Args:
     operating_account_type: the account type of the operating account.
     operating_account_id: the ID of the operating account.
     json_file: the JSON file containing event data.
     validate_only: whether to enable validate_only on the request.
     login_account_type: the account type of the login account.
     login_account_id: the ID of the login account.
     linked_account_type: the account type of the linked account.
     linked_account_id: the ID of the linked account.
    """

    # Gets an instance of the formatter.
    formatter: Formatter = Formatter()

    # Reads the input file.
    event_rows: List[Dict[str, Any]] = read_event_data_file(json_file)
    events: List[datamanager_v1.Event] = []
    for event_row in event_rows:
        event = datamanager_v1.Event()
        try:
            event_timestamp = Timestamp()
            event_timestamp.FromJsonString(str(event_row["timestamp"]))
            event.event_timestamp = event_timestamp
        except ValueError:
            _logger.warning(
                "Invalid timestamp format: %s. Skipping row.",
                event_row["timestamp"],
            )
            continue

        if "transactionId" not in event_row:
            _logger.warning("Skipping event with no transaction ID")
            continue
        event.transaction_id = event_row["transactionId"]

        if "eventSource" in event_row:
            event.event_source = event_row["eventSource"]

        if "gclid" in event_row:
            event.ad_identifiers = datamanager_v1.AdIdentifiers(
                gclid=event_row["gclid"]
            )

        if "currency" in event_row:
            event.currency = event_row["currency"]

        if "value" in event_row:
            event.conversion_value = event_row["value"]

        user_data = datamanager_v1.UserData()
        # Adds a UserIdentifier for each valid email address for the event row.
        if "emails" in event_row:
            for email in event_row["emails"]:
                try:
                    processed_email: str = formatter.process_email_address(
                        email, Encoding.HEX
                    )
                    user_data.user_identifiers.append(
                        datamanager_v1.UserIdentifier(
                            email_address=processed_email
                        )
                    )
                except ValueError:
                    # Skips invalid input.
                    _logger.warning(
                        "Invalid email address: %s. Skipping.",
                        event_row["email_address"],
                    )

        # Adds a UserIdentifier for each valid phone number for the event row.
        if "phoneNumbers" in event_row:
            for phone_number in event_row["phoneNumbers"]:
                try:
                    processed_phone: str = formatter.process_phone_number(
                        phone_number, Encoding.HEX
                    )
                    user_data.user_identifiers.append(
                        datamanager_v1.UserIdentifier(
                            phone_number=processed_phone
                        )
                    )
                except ValueError:
                    # Skips invalid input.
                    _logger.warning(
                        "Invalid phone: %s. Skipping.",
                        event_row["phone_number"],
                    )

        if user_data.user_identifiers:
            event.user_data = user_data

        # Adds the event to the list of events to send in the request.
        events.append(event)

    # Configures the destination.
    destination: datamanager_v1.Destination = datamanager_v1.Destination()
    destination.operating_account.account_type = operating_account_type
    destination.operating_account.account_id = operating_account_id
    destination.product_destination_id = str(conversion_action_id)
    if login_account_type or login_account_id:
        if bool(login_account_type) != bool(login_account_id):
            raise ValueError(
                "Must specify either both or neither of login "
                + "account type and login account ID"
            )
        destination.login_account.account_type = login_account_type
        destination.login_account.account_id = login_account_id
    if linked_account_type or linked_account_id:
        if bool(linked_account_type) != bool(linked_account_id):
            raise ValueError(
                "Must specify either both or neither of linked account "
                + "type and linked account ID"
            )
        destination.linked_account.account_type = linked_account_type
        destination.linked_account.account_id = linked_account_id

    # Creates a client for the ingestion service.
    client: datamanager_v1.IngestionServiceClient = (
        datamanager_v1.IngestionServiceClient()
    )

    # Batches requests to send up to the maximum number of events per
    # request.
    request_count = 0
    for i in range(0, len(events), _MAX_EVENTS_PER_REQUEST):
        request_count += 1
        events_batch = events[i : i + _MAX_EVENTS_PER_REQUEST]
        # Sends the request.
        request: datamanager_v1.IngestEventsRequest = (
            datamanager_v1.IngestEventsRequest(
                destinations=[destination],
                # Adds events from the current batch.
                events=events_batch,
                consent=datamanager_v1.Consent(
                    ad_user_data=datamanager_v1.ConsentStatus.CONSENT_GRANTED,
                    ad_personalization=datamanager_v1.ConsentStatus.CONSENT_GRANTED,
                ),
                # Sets encoding to match the encoding used.
                encoding=datamanager_v1.Encoding.HEX,
                # Sets validate_only. If true, then the Data Manager API only
                # validates the request but doesn't apply changes.
                validate_only=validate_only,
            )
        )

        # Sends the request.
        response: datamanager_v1.IngestEventsResponse = client.ingest_events(
            request=request
        )

        # Logs the response.
        _logger.info("Response for request #%d:\n%s", request_count, response)

    _logger.info("# of requests sent: %d", request_count)


def read_event_data_file(json_file: str) -> List[Dict[str, Any]]:
    """Reads the JSON-formatted event data file.
    Args:
      json_file: the event data file.
    """
    with open(json_file, "r") as f:
        return json.load(f)


if __name__ == "__main__":
    # Configures logging.
    logging.basicConfig(level=logging.INFO)

    parser = argparse.ArgumentParser(
        description=("Sends events from a JSON file to a destination."),
        fromfile_prefix_chars="@",
    )
    # The following argument(s) should be provided to run the example.
    parser.add_argument(
        "--operating_account_type",
        type=str,
        required=True,
        help="The account type of the operating account.",
    )
    parser.add_argument(
        "--operating_account_id",
        type=str,
        required=True,
        help="The ID of the operating account.",
    )
    parser.add_argument(
        "--conversion_action_id",
        type=int,
        required=True,
        help="The ID of the conversion action",
    )
    parser.add_argument(
        "--login_account_type",
        type=str,
        required=False,
        help="The account type of the login account.",
    )
    parser.add_argument(
        "--login_account_id",
        type=str,
        required=False,
        help="The ID of the login account.",
    )
    parser.add_argument(
        "--linked_account_type",
        type=str,
        required=False,
        help="The account type of the linked account.",
    )
    parser.add_argument(
        "--linked_account_id",
        type=str,
        required=False,
        help="The ID of the linked account.",
    )
    parser.add_argument(
        "--json_file",
        type=str,
        required=True,
        help="JSON file containing user data to ingest.",
    )
    parser.add_argument(
        "--validate_only",
        choices=["true", "false"],
        default="true",
        help="""Whether to enable validate_only on the request. Must be
        'true' or 'false'. Defaults to 'true'.""",
    )
    args = parser.parse_args()

    main(
        args.operating_account_type,
        args.operating_account_id,
        args.conversion_action_id,
        args.json_file,
        args.validate_only == "true",
        args.login_account_type,
        args.login_account_id,
        args.linked_account_type,
        args.linked_account_id,
    )

การตอบกลับที่สำเร็จ

คำขอที่สำเร็จจะแสดงการตอบกลับพร้อมออบเจ็กต์ที่มี requestId

{
  "requestId": "126365e1-16d0-4c81-9de9-f362711e250a"
}

บันทึก requestId ที่ส่งคืนเพื่อให้คุณเรียกข้อมูลการวินิจฉัย ได้เมื่อมีการประมวลผลปลายทางแต่ละแห่งในคำขอ

การตอบกลับที่ล้มเหลว

คำขอที่ไม่สำเร็จจะส่งผลให้เกิดรหัสสถานะการตอบกลับข้อผิดพลาด เช่น 400 Bad Request และการตอบกลับที่มีรายละเอียดข้อผิดพลาด

ตัวอย่างเช่น emailAddress ที่มีสตริงข้อความธรรมดาแทนค่าที่เข้ารหัสฐาน 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].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"
          }
        ]
      }
    ]
  }
}

ส่งเหตุการณ์สำหรับปลายทางหลายแห่ง

หากข้อมูลมีเหตุการณ์สําหรับปลายทางต่างๆ คุณสามารถส่งเหตุการณ์เหล่านั้นในคําขอเดียวกันได้โดยใช้การอ้างอิงปลายทาง

เช่น หากคุณมีเหตุการณ์สําหรับรหัสการกระทําที่ถือเป็น Conversion 123456789 และ อีกเหตุการณ์สําหรับรหัสการกระทําที่ถือเป็น Conversion 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": "PRODUCT_DESTINATION_ID",
      "reference": "destination_a"
    },
    {
      "operatingAccount": {
        "accountType": "OPERATING_ACCOUNT_2_TYPE",
        "accountId": "OPERATING_ACCOUNT_2_ID"
      },

      "loginAccount": {
        "accountType": "LOGIN_ACCOUNT_2_TYPE",
        "accountId": "LOGIN_ACCOUNT_2_ID"
      },

      "productDestinationId": "777111122",
      "reference": "destination_b"
    }
  ]

ตั้งค่า destinationReferences ของ Event แต่ละรายการเพื่อส่งไปยังปลายทางที่เฉพาะเจาะจงอย่างน้อย 1 แห่ง ตัวอย่างเช่น นี่คือ Event ที่ใช้กับ Destination แรกเท่านั้น 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 หรือ eventName เมื่อส่งเหตุการณ์ไปยังปลายทาง Google Analytics และรวมฟิลด์ Google Ads เช่น customVariables เมื่อส่งเหตุการณ์ไปยังปลายทาง Google Ads

ขั้นตอนถัดไป