請詳閱本指南,熟悉如何使用 Data Manager API 傳送事件。如需支援的用途清單,請參閱「事件總覽」。
在本指南中,您將完成下列步驟:
- 準備
Destination接收事件資料。 - 準備要傳送的事件資料。
- 為事件建立
IngestionService要求。 - 使用 Google APIs Explorer 傳送要求。
- 瞭解成功和失敗的回應。
準備目的地
您必須先準備至少一個Destination,才能傳送資料。以下是 Destination 的欄位。如需更多詳細資料和不同情境的目標範例,請參閱「設定目的地」。
選取符合您用途的分頁。
- 如果您使用 Google 帳戶的憑證,且該帳戶是您要管理的廣告主帳戶使用者,請選取「廣告主」。
- 如果您使用 Google 帳戶的憑證,而該帳戶是資料合作夥伴帳戶中的使用者,且您想管理與資料合作夥伴帳戶有合作夥伴連結的廣告主帳戶,請選取「資料合作夥伴」。資料合作夥伴帳戶必須通過核准程序,才能取得帳戶。如要開始使用,請填寫意願調查表。
廣告主
operatingAccount接收事件的帳戶。
如果是多來源轉換或事件,作業帳戶可以是 Google Ads 帳戶、Google Analytics 資源或 Floodlight 設定。
如果
accountType為GOOGLE_ANALYTICS_PROPERTY,要求憑證必須屬於具備資源編輯者或管理員角色的 Google Analytics 使用者。如果是 Google Ads 離線轉換、待開發客戶強化轉換、店面銷售轉換,以及透過購物車資料回報的轉換,作業帳戶必須是擁有轉換動作的 Google Ads 帳戶。
如果是 Google Marketing Platform 轉換,作業帳戶必須是 Floodlight 設定。
loginAccount憑證的 Google 帳戶是使用者的帳戶。
productDestinationId接收事件的
operatingAccount中實體的 ID。如果是多來源轉換或事件,
productDestinationId必須是下列其中一項:Google Ads 轉換的 ID,其中
type設為WEBPAGE。在 Google Ads 使用者介面中,轉換動作的轉換來源為「網站」。WEBPAGEGoogle Analytics 網站資料串流的評估 ID。
Google Analytics iOS 或 Android 應用程式資料串流的 Firebase 應用程式 ID。
Floodlight 轉換的 Floodlight 活動 ID。
傳送至 Google Analytics 的其他事件,
productDestinationId必須是下列其中一項:Google Analytics 網站資料串流的評估 ID。
Google Analytics iOS 或 Android 應用程式資料串流的 Firebase 應用程式 ID。
如果是 Google Ads 離線轉換或待開發客戶強化轉換,
productDestinationId必須是 Google Ads 轉換動作的 ID,且type設為UPLOAD_CLICKS。在 Google Ads 使用者介面中,UPLOAD_CLICKS轉換動作的「轉換來源」為「網站 (從點擊匯入)」。如果是商店銷售轉換,
productDestinationId必須是 Google Ads 轉換動作的 ID,且type設為STORE_SALES。在 Google Ads 使用者介面中,STORE_SALES轉換動作的「轉換來源」為「商店銷售」。如果是 Floodlight 離線轉換,請將
productDestinationId設為 Floodlight 活動 ID。
{
"operatingAccount": {
"accountType": "OPERATING_ACCOUNT_TYPE",
"accountId": "OPERATING_ACCOUNT_ID"
},
"loginAccount": {
"accountType": "LOGIN_ACCOUNT_TYPE",
"accountId": "LOGIN_ACCOUNT_ID"
},
"productDestinationId": "PRODUCT_DESTINATION_ID"
}
資料合作夥伴
operatingAccount- 接收事件的帳戶。必須是 Google Ads 帳戶。Data Manager API 不支援使用已連結的資料合作夥伴,將事件傳送至 Google Analytics 資源或 Floodlight 設定。
loginAccount憑證使用者可存取的帳戶。
將
accountId設為資料合作夥伴帳戶 ID,並將accountType設為DATA_PARTNER。合作夥伴要求獲得核准後,Google 就會提供資料合作夥伴帳戶 ID。linkedAccount透過已建立合作夥伴連結的帳戶,憑證使用者可存取
operatingAccount。如果
operatingAccount的父項已連結至資料合作夥伴帳戶,請將linkedAccount設為operatingAccount的父項。如果operatingAccount直接連結至資料合作夥伴帳戶,請勿設定linkedAccount。productDestinationIdoperatingAccount中接收事件的實體 ID。如果是透過已連結的資料合作夥伴傳送多來源事件,
productDestinationId必須是 Google Ads 轉換的 ID,且type設為WEBPAGE。在 Google Ads 使用者介面中,轉換動作的轉換來源為「網站」。WEBPAGE如果是 Google Ads 離線轉換或待開發客戶強化轉換,
productDestinationId必須是 Google Ads 轉換動作的 ID,且type設為UPLOAD_CLICKS。在 Google Ads 使用者介面中,UPLOAD_CLICKS轉換動作的「轉換來源」為「網站 (從點擊匯入)」。如果是商店銷售轉換,
productDestinationId必須是 Google Ads 轉換動作的 ID,且type設為STORE_SALES。在 Google Ads 使用者介面中,轉換來源為「商店銷售」的轉換動作會顯示為STORE_SALES。
{
"operatingAccount": {
"accountType": "OPERATING_ACCOUNT_TYPE",
"accountId": "OPERATING_ACCOUNT_ID"
},
"loginAccount": {
"accountType": "DATA_PARTNER",
"accountId": "DATA_PARTNER_ACCOUNT_ID"
},
"linkedAccount": {
"accountType": "LINKED_ACCOUNT_TYPE",
"accountId": "LINKED_ACCOUNT_ID"
},
"productDestinationId": "PRODUCT_DESTINATION_ID"
}
本指南中的範例說明如何建構要求,將所有事件傳送至相同目的地。如要在同一個要求中傳送多個目的地的事件,請參閱「傳送多個目的地的事件」。
準備事件資料
請看下列事件資料。每個表格都對應一個轉換事件。每個轉換事件都有事件的時間戳記、轉換動作和轉換價值。
每個事件可能都有廣告 ID (例如 gclid) 或使用者 ID (例如電子郵件地址、電話號碼和地址資訊)。事件也可以包含:
- 事件發生時評估的使用者相關資訊,例如顧客價值,或是使用者是新顧客、回訪顧客還是回流顧客。
- 購物車資料。
- 目的地適用的其他事件參數或使用者屬性,例如 Google Analytics 的
clientId、appInstanceId或userId。
以下是事件資料:
事件 1
| 活動 #1 | |
|---|---|
conversion_time |
2025-06-10 15:07:01-05:00 |
conversion_action_id |
123456789 |
transaction_id |
ABC798654321 |
conversion_value |
30.03 |
currency |
USD |
gclid |
GCLID_1 |
emails |
|
given_name |
John |
family_name |
Smith-Jones |
region_code |
us |
postal_code |
94045 |
customer_type |
NEW |
customer_value_bucket |
HIGH |
client_id |
1234567890.1761581763 |
user_id |
user_ABC12345 |
ad_unit_name |
Banner_01 |
event_name |
purchase |
| 購物車商品 | |
item_id |
SKU_12345 |
item_name |
Stan and Friends Tee |
item_affiliation |
Google Merchandise Store |
item_coupon |
SUMMER_FUN |
item_discount |
2.22 |
item_index |
0 |
item_brand |
Google |
item_category |
Apparel |
item_category2 |
Adult |
item_category3 |
Shirts |
item_category4 |
Crew |
item_category5 |
Short sleeve |
item_list_id |
related_products |
item_list_name |
Related Products |
item_price |
10.01 |
item_quantity |
3 |
事件 2
| 活動 #2 | |
|---|---|
conversion_time |
June 10, 2025 11:42:33PM America/New_York |
conversion_action_id |
123456789 |
transaction_id |
DEF999911111 |
conversion_value |
42.02 |
currency |
eur |
gclid |
GCLID_2 |
emails |
|
given_name |
zoë |
family_name |
pérez |
region_code |
US |
postal_code |
94043 |
address_line |
1800 Amphibious Blvd. |
city |
Mountain View |
administrative_area |
CA |
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 |
事件 3
| 活動 #3 (商店銷售) | |
|---|---|
conversion_time |
June 11, 2025 12:05:00PM America/New_York |
conversion_action_id |
123456789 |
transaction_id |
XYZ987654321 |
conversion_value |
450.0 |
currency |
usd |
given_name |
Charlie |
family_name |
Smith-Jones |
region_code |
US |
postal_code |
10011 |
store_id |
STORE_NY_001 |
merchant_id |
123 |
merchant_feed_label |
US |
merchant_feed_language_code |
en |
| 購物車商品 | |
merchant_product_id |
ITEM_SKU_987 |
item_quantity |
1 |
item_price |
450.0 |
variable |
DEPARTMENT |
value |
ELECTRONICS |
設定資料格式
按照格式設定指南的規定設定欄位格式。以下是格式化後的事件資料:
事件 1
| 活動 #1 | |
|---|---|
conversion_time |
2025-06-10T15:07:01-05:00 |
conversion_action_id |
123456789 |
transaction_id |
ABC798654321 |
conversion_value |
30.03 |
currency |
USD |
gclid |
GCLID_1 |
emails |
|
given_name |
john |
family_name |
smith-jones |
region_code |
US |
postal_code |
94045 |
customer_type |
NEW |
customer_value_bucket |
HIGH |
client_id |
1234567890.1761581763 |
user_id |
user_ABC12345 |
ad_unit_name |
Banner_01 |
event_name |
purchase |
| 購物車商品 | |
item_id |
SKU_12345 |
item_name |
Stan and Friends Tee |
item_affiliation |
Google Merchandise Store |
item_coupon |
SUMMER_FUN |
item_discount |
2.22 |
item_index |
0 |
item_brand |
Google |
item_category |
Apparel |
item_category2 |
Adult |
item_category3 |
Shirts |
item_category4 |
Crew |
item_category5 |
Short sleeve |
item_list_id |
related_products |
item_list_name |
Related Products |
item_price |
10.01 |
item_quantity |
3 |
事件 2
| 活動 #2 | |
|---|---|
conversion_time |
2025-06-10T23:42:33-05:00 |
conversion_action_id |
123456789 |
transaction_id |
DEF999911111 |
conversion_value |
42.02 |
currency |
EUR |
gclid |
GCLID_2 |
emails |
|
given_name |
zoë |
family_name |
pérez |
region_code |
US |
postal_code |
94043 |
address_line |
1800 amphibious blvd |
city |
mountain view |
administrative_area |
ca |
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 |
事件 3
| 活動 #3 (商店銷售) | |
|---|---|
conversion_time |
2025-06-11T12:05:00-04:00 |
conversion_action_id |
123456789 |
transaction_id |
XYZ987654321 |
conversion_value |
450.0 |
currency |
USD |
given_name |
charlie |
family_name |
smith-jones |
region_code |
US |
postal_code |
10011 |
store_id |
STORE_NY_001 |
merchant_id |
123 |
merchant_feed_label |
US |
merchant_feed_language_code |
en |
| 購物車商品 | |
merchant_product_id |
ITEM_SKU_987 |
item_quantity |
1 |
item_price |
450.0 |
variable |
DEPARTMENT |
value |
ELECTRONICS |
將資料雜湊及編碼
此外,格式化後的電子郵件地址、名字和姓氏必須使用 SHA-256 演算法進行雜湊處理,並以十六進位或 Base64 編碼。以下是經過格式化、雜湊處理和編碼 (使用十六進位編碼) 的事件資料:
事件 1
| 活動 #1 | |
|---|---|
conversion_time |
2025-06-10T15:07:01-05:00 |
conversion_action_id |
123456789 |
transaction_id |
ABC798654321 |
conversion_value |
30.03 |
currency |
USD |
gclid |
GCLID_1 |
emails |
|
given_name |
96D9632F363564CC3032521409CF22A852F2032EEC099ED5967C0D000CEC607A |
family_name |
DB98D2607EFFFA28AFF66975868BF54C075ECA7157E35064DCE08E20B85B1081 |
region_code |
US |
postal_code |
94045 |
customer_type |
NEW |
customer_value_bucket |
HIGH |
client_id |
1234567890.1761581763 |
user_id |
user_ABC12345 |
ad_unit_name |
Banner_01 |
event_name |
purchase |
| 購物車商品 | |
item_id |
SKU_12345 |
item_name |
Stan and Friends Tee |
item_affiliation |
Google Merchandise Store |
item_coupon |
SUMMER_FUN |
item_discount |
2.22 |
item_index |
0 |
item_brand |
Google |
item_category |
Apparel |
item_category2 |
Adult |
item_category3 |
Shirts |
item_category4 |
Crew |
item_category5 |
Short sleeve |
item_list_id |
related_products |
item_list_name |
Related Products |
item_price |
10.01 |
item_quantity |
3 |
事件 2
| 活動 #2 | |
|---|---|
conversion_time |
2025-06-10T23:42:33-05:00 |
conversion_action_id |
123456789 |
transaction_id |
DEF999911111 |
conversion_value |
42.02 |
currency |
EUR |
gclid |
GCLID_2 |
emails |
|
given_name |
2752B88686847FA5C86F47B94CE652B7B3F22A91C37617D451A4DB9AFA431450 |
family_name |
6654977D57DDDD3C0329CA741B109EF6CD6430BEDD00008AAD213DF25683D77F |
region_code |
US |
postal_code |
94043 |
address_line |
FF75E73A0E768CC1FA28A64FAEBBCECCB562D7C05F2FFCDD8D100ABAD73E4579 |
city |
mountain view |
administrative_area |
ca |
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 |
事件 3
| 活動 #3 (商店銷售) | |
|---|---|
conversion_time |
2025-06-11T12:05:00-04:00 |
conversion_action_id |
123456789 |
transaction_id |
XYZ987654321 |
conversion_value |
450.0 |
currency |
USD |
given_name |
B9DD960C1753459A78115D3CB845A57D924B6877E805B08BD01086CCDF34433C |
family_name |
DB98D2607EFFFA28AFF66975868BF54C075ECA7157E35064DCE08E20B85B1081 |
region_code |
US |
postal_code |
10011 |
store_id |
STORE_NY_001 |
merchant_id |
123 |
merchant_feed_label |
US |
merchant_feed_language_code |
en |
| 購物車商品 | |
merchant_product_id |
ITEM_SKU_987 |
item_quantity |
1 |
item_price |
450.0 |
variable |
DEPARTMENT |
value |
ELECTRONICS |
將資料轉換為 Event 物件
將每個事件的格式化和雜湊處理資料轉換為 Event。請按照指示填入下列欄位:
將
eventTimestamp設為事件發生的時間。Google Analytics 事件的
eventTimestamp必須在過去 72 小時內。此外,您必須在原始用戶端事件時間戳記的 48 小時內,傳送要與 Google Analytics for Firebase SDK 或 gtag.js 收集的事件合併或一併處理的 Google Analytics 事件。Google Analytics 可能無法如預期處理在此時間之後傳送的事件,尤其是轉換歸因。為 Google Ads、Google Analytics 或 Floodlight 用途設定必填欄位。
Google Ads
用途 ID transactionIdeventSource附註 Google Ads 離線轉換或待開發客戶強化轉換 必填。設定至少下列其中一項: adIdentifiers至少要設定gclid、gbraid或wbraid,或landingPageDeviceInfo.ipAddress- 工作階段屬性
userDataeventDeviceInfo.ipAddress
選用 必填。設為 EventSource的其中一個列舉值。多來源轉換 必填。設定至少下列其中一項: adIdentifiers至少要設定gclid、gbraid或wbraid,或landingPageDeviceInfo.ipAddress- 工作階段屬性
userDataeventDeviceInfo.ipAddress
必要 (選用步驟) 如要設定,必須為 WEB。- 查看事件比對和重複資料刪除。
- 如果您傳送透過購物車資料回報的 Google Ads 轉換,請在事件中加入購物車資料。
商店銷售轉換 - 必填。設定
Event的下列欄位:eventLocation.storeIdcurrencyconversionValue
- 必填。設定下列欄位來提供使用者 ID:
- 如果使用者 ID 來自第三方來源,請填入
thirdPartyUserData。只有在Destination的登入帳戶是資料合作夥伴 (loginAccount.accountType為DATA_PARTNER) 時,才能設定thirdPartyUserData。 - 否則,請填入
userData。
- 如果使用者 ID 來自第三方來源,請填入
必要 必填。請設為 IN_STORE。如要加入購物車資訊,請將購物車資料新增至事件。 Google Analytics
用途 ID transactionIdeventSource附註 傳送至網站資料串流的多來源事件 必填。設定至少下列其中一項: clientIdadIdentifiers已設定gcliduserIduserData
必要 (選用步驟) 如要設定,必須為 WEB。- 查看事件比對和重複資料刪除。
- 在事件中加入 Google Analytics 資訊。
- 如果您傳送需要
items清單的 Google Analytics 建議事件 (例如purchase),請新增購物車資料。
傳送至應用程式資料串流的多來源事件 必填。設定
appInstanceId。設定選填欄位,例如
userId和adIdentifiers,並使用gbraid或gclid進一步提升評估成效。必要 (選用步驟) 如要設定,必須為 APP。- 查看事件比對和重複資料刪除。
- 在事件中加入 Google Analytics 資訊。
- 如果您傳送需要
items清單的 Google Analytics 建議事件 (例如purchase),請新增購物車資料。
傳送至網站資料串流的其他事件 必填。設定 clientId。部分活動 (例如 purchase和refund) 必須提供這項資訊。詳情請參閱特定活動的規定。(選用步驟) 如要設定,必須為 WEB。- 在事件中加入 Google Analytics 資訊。
- 如果您傳送需要
items清單的 Google Analytics 建議事件 (例如purchase),請新增購物車資料。
傳送至應用程式資料串流的其他事件 必填。設定 appInstanceId。部分活動 (例如 purchase和refund) 必須提供這項資訊。詳情請參閱特定活動的規定。(選用步驟) 如要設定,必須為 APP。- 在事件中加入 Google Analytics 資訊。
- 如果您傳送需要
items清單的 Google Analytics 建議事件 (例如purchase),請新增購物車資料。
泛光燈
用途 ID transactionIdeventSource附註 離線轉換 必填項目。將
adIdentifiers設為gclid、matchId、dclid、impressionId、mobileDeviceId或encryptedUserIds其中一項。將
userData設為提供使用者 ID。選用 選用 如要在 Floodlight 轉換中加入購物車資訊,請新增購物車資料。 多來源轉換 必填項目。將
adIdentifiers設為gclid、matchId、dclid、impressionId、mobileDeviceId或encryptedUserIds其中一項。將
userData設為提供使用者 ID。必要 選用 如要在 Floodlight 轉換中加入購物車資訊,請新增購物車資料。 填入事件的其他欄位值。如需可用欄位的完整清單,請參閱
Event參考文件。
事件比對和刪除重複資料
Google Ads
在同一個轉換動作中,Google Ads 會使用
transactionId
刪除重複的轉換事件。
如果上傳的事件與現有交易 ID 相符,Google Ads 會將其視為轉換調整項 (例如重新陳述轉換價值或補充使用者資料)。如果找不到相符項目,系統會將其視為新的轉換事件。
如要瞭解系統如何處理相符和不相符事件的欄位值,請參閱「轉換調整項」。
Google Analytics
在同一個事件和資源中,Google Analytics 會使用
transactionId
從不同來源 (例如網站代碼和 Data Manager API 擷取要求) 排除重複事件。Google Analytics 會使用收到的第一個相同事件例項中的資訊。
泛光燈
Data Manager API 會結合 Floodlight 活動 ID 和 transactionId,從不同來源 (例如網站代碼和 Data Manager API 擷取要求) 排除重複事件。transactionId 欄位必須設為每個事件的專屬 ID。
新增工作階段屬性
如果您傳送離線轉換或待開發客戶強化轉換,請在沒有其他廣告 ID (例如 Google 點擊 ID 或 WBRAID) 時,加入工作階段屬性。除了其他廣告 ID 之外,您也可以納入工作階段屬性。
工作階段屬性可提供使用者與網站互動的額外背景資訊和信號,有助於提升轉換評估、報表和出價的準確度。
選擇傳送工作階段屬性的方法:
建議:將
adIdentifiers的sessionAttributes欄位設為在表單提交中擷取的 Base64 編碼工作階段屬性字串。替代做法:擷取個別工作階段屬性欄位,並在
experimentalFields和adIdentifiers欄位中,將每個屬性新增至事件。只有在無法擷取編碼的工作階段屬性字串時,才使用這個選項。
建議
將 adIdentifiers 的 sessionAttributes 欄位設為採用 Base64 編碼的工作階段屬性字串。請按照「如何擷取 session_attributes」一文中的操作說明,修改表單提交頁面,擷取編碼字串。
以下是範例事件的部分內容,其中 sessionAttributes 欄位包含編碼的工作階段屬性:
{
...,
"adIdentifiers": {
"sessionAttributes": "INSERT_BASE64_ENCODED_SESSION_ATTRIBUTES_STRING_HERE"
}
}
另類
如果無法擷取已編碼的工作階段屬性字串,請為 experimentalFields 清單中的每個工作階段屬性新增 ExperimentalField。此外,請將使用者代理程式新增至 adIdentifiers 欄位的 landingPageDeviceInfo 欄位。
experimentalFields:必要:為每個必要鍵,在
experimentalFields中新增鍵/值組合項目:gad_source:gad_campaignidsession_start_time_usec
此外,您也可以為每個選用鍵新增鍵/值組合:
landing_page_urllanding_page_referrer
adIdentifiers:必要:在
userAgent欄位中加入到達網頁使用者代理程式。adIdentifiers.landingPageDeviceInfo
以下是範例事件的部分內容,其中包含 experimentalFields、gad_source、gad_campaignid 和 session_start_time_usec 的項目,以及 landingPageDeviceInfo 欄位中的使用者代理程式:
{
...,
"experimentalFields": [
{
"field": "gad_source",
"value": "1"
},
{
"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"
}
}
}
新增加密的使用者 ID
如果您要將轉換資料傳送至 Floodlight,可以使用加密的使用者 ID 識別使用者。如果您使用加密的使用者 ID,也必須提供加密方式的詳細資料。
將 EncryptedUserId 物件新增至活動的 adIdentifiers encryptedUserIds 清單。
必填欄位包括:
encryptedId- 必要。加密的使用者 ID。
source- 必要。加密使用者 ID 的來源。有效值為
AD_SERVING(來自%m比對巨集的 ID) 和DATA_TRANSFER(來自資料移轉檔案的 ID)。 entityId- 必要。用於加密使用者 ID 的實體 ID。如果來源是
DATA_TRANSFER,這通常是 Campaign Manager 360 帳戶 ID;如果來源是AD_SERVING,這通常是 Campaign Manager 360 廣告主 ID。 entityType- 必要。用於加密使用者 ID 的實體類型,指定為
EncryptionEntityType列舉值。
新增 Google Analytics 資訊
如果多來源事件的目的地包含 Google Analytics 資源,請按照指示填入下列欄位:
eventName必填。Google Analytics 事件的名稱。
如果事件名稱是預留名稱,Data Manager API 會拒絕該事件,並傳回
INVALID_EVENT_NAME錯誤。transactionId如果Google Analytics 事件規格中指出事件需要這個參數,則為必要參數。
- ID
以下是各類型資料串流的 ID 規定:
網站資料串流
如果傳送含有交易 ID 的事件做為多來源事件,則必須設定下列至少一個欄位:
clientId:網頁用戶端使用者執行個體的專屬 ID。 請參閱 Google Analytics Measurement Protocol 操作說明。userId:使用者的專屬 ID。詳情請參閱「利用 User-ID 評估跨平台活動」。userData:使用者提供的資料,例如電子郵件地址、電話號碼和地址,代表與事件相關聯的使用者。如需如何正規化及雜湊處理值的指引,請參閱「格式化使用者資料」。
如果是其他建議或自訂事件,則必須使用
clientId。請參閱 Google Analytics Measurement Protocol 操作說明。應用程式資料串流
appInstanceId:必要。設為應用程式用戶端使用者執行個體的專屬 ID。請參閱 Google Analytics Measurement Protocol 操作說明。
destinationReferences如果要求層級的
destinations清單包含多個 Google AnalyticsDestination,則為必填。在destinationReferences中新增項目,指定應接收事件的 Google Analytics 目的地。如要進一步瞭解目的地參照,請參閱「將事件傳送至多個目的地」。如果未設定
destinationReferences,或有多個參照 Google Analytics 目的地的項目,Data Manager API 會拒絕事件,並傳回MULTIPLE_DESTINATIONS_FOR_GOOGLE_ANALYTICS_EVENT錯誤。cartData如果Google Analytics 事件規格中指出事件需要這個參數,則為必要參數。
瞭解如何將購物車資料新增至事件。
userId(選用步驟) 使用者的 User-ID。
userData(選用步驟) 使用者提供的資料,例如電子郵件地址、電話號碼和地址,代表與事件相關聯的使用者。如需如何正規化及雜湊處理值的指引,請參閱「格式化使用者資料」。
userProperties(選用步驟) 使用者的使用者屬性。為每個使用者屬性,在
additionalUserProperties清單中新增個別項目。additionalEventParameters(選填,但建議使用) 如果其他
Event欄位未擷取到任何 Google Analytics 事件參數,請填入這份清單。參數可以包含事件的其他建議參數,或是您想擷取的任何其他參數。使用EventParameter的parameterNameGoogle Analytics 參數名稱。舉例來說,如果交易有相關稅金,請在
additionalEventParameters中新增項目,並將parameterName設為tax,以及將value設為稅金費用。我們不建議在
additionalEventParameters中新增項目,因為這會導致transactionId、currency或valueGoogle Analytics 事件參數發生問題。請改為填入Event的transactionId、currency和conversionValue欄位,這些欄位的優先順序高於additionalEventParameters中的任何項目。
新增購物車資料
在 Event 的 cartData 欄位中,填入與事件相關聯的商品資訊。傳送 Google Analytics 建議事件 (例如需要 items 清單的 purchase)、Google Ads 含有購物車資料參數的轉換、Google Ads 商店銷售轉換或 Floodlight 轉換時,請使用這個欄位。
視需要填入 CartData 的下列欄位。
items- 必要。這份清單中至少要包含一個項目。
Merchant Center 欄位
merchantId、merchantFeedLabel 和 merchantFeedLanguageCode 全為選用。如果事件中的項目包含多個 Merchant Center 帳戶中的產品,請為「透過購物車資料回報的 Google Ads 轉換」設定這些欄位。
merchantId- 選用。Merchant Center 帳戶 ID。
merchantFeedLabel- 選用。Merchant Center 動態饋給的動態饋給標籤。動態饋給標籤可讓你將產品分類,以利設定廣告活動指定目標。舉例來說,你可以使用動態饋給標籤,依語言整理產品。如果廣告活動會根據國家/地區指定產品,請使用 ISO-3166-1 alpha-2 格式的 2 個字母國家/地區代碼。範例:
US。 merchantFeedLanguageCode- 選用。與上傳商品所在 Merchant Center 動態饋給相關聯的 ISO 639-1 語言代碼。範例:
en。
新增商品層級購物車資料
將一或多個 Item 物件新增至 CartData 的 items 清單。如果 Google Ads 轉換含有購物車資料,且 Google Analytics 建議事件 (例如 purchase) 需要 items 清單,則該
items清單不得為空白。
視需要填入每個 Item 的下列欄位。
Google Analytics
以下是 Google Analytics 建議事件 (例如 purchase) 的規定,這類事件需要 items 清單。
items.itemId- 必要。商品的專屬 ID。
items.unitPrice選用。 此為單價,不含稅金、運費和這項商品的事件範圍 (交易層級) 折扣。
如果商品有商品範圍折扣,請使用折扣單價。舉例來說,如果商品的單價為
27.67,單價折扣為6.66,則請將unitPrice設為21.01。items.quantity必填。這項商品的單位數量。
items.additionalItemParameters(選用步驟) 請使用其他
Item欄位未擷取的任何以商品為範圍參數,填入這份清單。請使用 Google Analytics 說明文件中的參數名稱做為parameterNameItemParameter。舉例來說,如果是 Google Analytics,如果項目有
purchase事件的品牌和類別,請在項目的additionalItemParameters中新增項目,並將parameterName設為item_brand,以及將value設為品牌名稱。我們不建議為
quantity、price或item_id項目參數將項目新增至additionalItemParameters。請改為填入Item的itemId、unitPrice和quantity欄位,這些欄位的優先順序高於additionalItemParameters中的任何項目。
Google Ads 商店銷售
以下是 Google Ads 商店銷售轉換的相關規定。
items.itemId- 選用。商品的專屬 ID。
items.merchantProductId- 選用。Merchant Center 帳戶中的產品 ID。
items.quantity- 選用。這項特定商品的購買數量。如果未設定,則預設值為
1。 items.unitPrice(選用步驟) 設為此項目的單價,不含稅金、運費和事件範圍 (交易層級) 折扣。
如果商品有商品範圍折扣,請使用折扣單價。舉例來說,如果商品的單價為
27.67,單一折扣為6.66,則請將unitPrice設為21.01。items.conversionValue(選用步驟) 這項商品的單一單位轉換價值。舉例來說,如果購買兩件商品,每件價格為 $10.00 美元,請將
items.conversionValue設為10.00,並將items.quantity設為2。請注意,雖然個別項目可不填寫這個欄位,但上傳商店銷售轉換時,必須填寫事件層級的conversionValue欄位。items.customVariables(選用步驟) 要傳送至 Google Ads 轉換動作的其他鍵/值組合資料,指定為
CustomVariable物件清單。
透過購物車資料回報的 Google Ads 轉換
如要透過非商店銷售轉換的 Google Ads 轉換傳送購物車資料,請務必遵守下列規定。如果您的事件是 Google Ads 商店銷售轉換,請參閱 Google Ads 商店銷售規定。
items.itemId- 必要。商品的專屬 ID。
items.merchantProductId- 必要。Merchant Center 帳戶中的產品 ID。
items.unitPrice必要。 此為單位價格,不含稅金、運費和這項商品的事件範圍 (交易層級) 折扣。
如果商品有商品範圍折扣,請使用折扣單價。舉例來說,如果商品的單價為
27.67,單價折扣為6.66,則請將unitPrice設為21.01。items.quantity必填。這項特定商品購買的數量。
泛光燈
以下是將事件傳送至 Floodlight 的規定。
items.itemId- 選用。項目的專屬 ID。
items.merchantProductId- 必要。Merchant Center 帳戶中的產品 ID。
items.unitPrice必要。 此為單價,不含稅金、運費和這項商品的事件範圍 (交易層級) 折扣。
如果商品有商品範圍折扣,請使用折扣單價。舉例來說,如果商品的單價為
27.67,單價折扣為6.66,則請將unitPrice設為21.01。items.quantity必填。這項特定商品購買的數量。
要求範例
Google Analytics
以下是第二個事件中,經過格式化、雜湊處理和編碼的資料範例 Event,以及 Google Analytics 的額外資料:
{
"adIdentifiers": {
"gclid": "GCLID_2"
},
"conversionValue": 42.02,
"currency": "EUR",
"eventTimestamp": "2025-06-10T23:42:33-05:00",
"transactionId": "DEF999911111",
"eventSource": "WEB",
"userData": {
"userIdentifiers": [
{
"emailAddress": "3E693CF7E5B67880BFF33B2D2626DADB7BF1D4BC737192E47CF8BAA89ACF2250"
},
{
"emailAddress": "223EBDA6F6889B1494551BA902D9D381DAF2F642BAE055888E96343D53E9F9C4"
},
{
"address": {
"givenName": "2752B88686847FA5C86F47B94CE652B7B3F22A91C37617D451A4DB9AFA431450",
"familyName": "6654977D57DDDD3C0329CA741B109EF6CD6430BEDD00008AAD213DF25683D77F",
"regionCode": "US",
"postalCode": "94043",
"addressLine": "FF75E73A0E768CC1FA28A64FAEBBCECCB562D7C05F2FFCDD8D100ABAD73E4579",
"city": "mountain view",
"administrativeArea": "ca"
}
}
],
},
"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"
}
]
}
]
}
}
Google Ads 商店銷售
以下是第三個事件的格式化、雜湊處理和編碼資料範例 Event,可做為 Google Ads 商店銷售轉換的範例:
{
"conversionValue": 450.0,
"currency": "USD",
"eventTimestamp": "2025-06-11T12:05:00-04:00",
"transactionId": "XYZ987654321",
"eventSource": "IN_STORE",
"eventLocation": {
"storeId": "STORE_NY_001"
},
"userData": {
"userIdentifiers": [
{
"address": {
"givenName": "B9DD960C1753459A78115D3CB845A57D924B6877E805B08BD01086CCDF34433C",
"familyName": "DB98D2607EFFFA28AFF66975868BF54C075ECA7157E35064DCE08E20B85B1081",
"regionCode": "US",
"postalCode": "10011"
}
}
]
},
"cartData": {
"merchantId": "123",
"merchantFeedLabel": "US",
"merchantFeedLanguageCode": "en",
"items": [
{
"merchantProductId": "ITEM_SKU_987",
"quantity": 1,
"conversionValue": 450.0,
"customVariables": [
{
"variable": "DEPARTMENT",
"value": "ELECTRONICS"
}
]
}
]
}
}
建立要求主體
如要建構要求主體,請合併 destinations 和 events,設定 encoding 欄位,並加入您想納入的任何其他要求欄位,例如 validateOnly 和 consent。
本指南中的範例不會使用加密功能,但您可以按照「加密使用者資料」一文中的說明,在程序中加入加密功能。
傳送要求
如要透過瀏覽器提出要求,請按照下列步驟操作:
- 選取「REST」分頁,然後按一下「Open in API Explorer」,在新分頁或視窗中開啟 API Explorer。
- 在 API Explorer 的要求主體中,將開頭為
REPLACE_WITH的每個字串 (例如REPLACE_WITH_OPERATING_ACCOUNT_TYPE) 替換為相關值。 - 按一下 API Explorer 頁面底部的「執行」,然後完成授權提示,即可傳送要求。
- 將
validateOnly設為true,即可驗證要求,但不會套用變更。準備好套用變更時,請將validateOnly設為false。
如果您已安裝用戶端程式庫,請選取所選程式設計語言的分頁,查看如何建構及傳送要求的完整程式碼範例。
REST
廣告主
{ "destinations": [ { "operatingAccount": { "accountType": "OPERATING_ACCOUNT_TYPE", "accountId": "OPERATING_ACCOUNT_ID" }, "loginAccount": { "accountType": "LOGIN_ACCOUNT_TYPE", "accountId": "LOGIN_ACCOUNT_ID" }, "productDestinationId": "CONVERSION_ACTION_ID" } ], "encoding": "HEX", "events": [ { "adIdentifiers": { "gclid": "GCLID_1" }, "conversionValue": 30.03, "currency": "USD", "eventTimestamp": "2025-06-10T20:07:01Z", "transactionId": "ABC798654321", "eventSource": "WEB", "userData": { "userIdentifiers": [ { "address": { "givenName": "96D9632F363564CC3032521409CF22A852F2032EEC099ED5967C0D000CEC607A", "familyName": "DB98D2607EFFFA28AFF66975868BF54C075ECA7157E35064DCE08E20B85B1081", "regionCode": "US", "postalCode": "94045" } } ] }, "userProperties": { "customerType": "NEW", "customerValueBucket": "HIGH" }, "eventName": "purchase", "clientId": "1234567890.1761581763", "userId": "user_ABC12345", "additionalEventParameters": [ { "parameterName": "ad_unit_name", "value": "Banner_01" } ], "cartData": { "transactionDiscount": 6.66, "items": [ { "itemId": "SKU_12345", "quantity": 3, "unitPrice": 10.01, "additionalItemParameters": [ { "parameterName": "item_name", "value": "Stan and Friends Tee" }, { "parameterName": "affiliation", "value": "Google Merchandise Store" }, { "parameterName": "coupon", "value": "SUMMER_FUN" }, { "parameterName": "discount", "value": "2.22" }, { "parameterName": "index", "value": "0" }, { "parameterName": "item_brand", "value": "Google" }, { "parameterName": "item_category", "value": "Apparel" }, { "parameterName": "item_category2", "value": "Adult" }, { "parameterName": "item_category3", "value": "Shirts" }, { "parameterName": "item_category4", "value": "Crew" }, { "parameterName": "item_category5", "value": "Short sleeve" }, { "parameterName": "item_list_id", "value": "related_products" }, { "parameterName": "item_list_name", "value": "Related Products" } ] } ] } }, { "adIdentifiers": { "gclid": "GCLID_2" }, "conversionValue": 42.02, "currency": "EUR", "eventTimestamp": "2025-06-11T04:42:33Z", "transactionId": "DEF999911111", "eventSource": "WEB", "userData": { "userIdentifiers": [ { "emailAddress": "3E693CF7E5B67880BFF33B2D2626DADB7BF1D4BC737192E47CF8BAA89ACF2250" }, { "emailAddress": "223EBDA6F6889B1494551BA902D9D381DAF2F642BAE055888E96343D53E9F9C4" }, { "address": { "givenName": "2752B88686847FA5C86F47B94CE652B7B3F22A91C37617D451A4DB9AFA431450", "familyName": "6654977D57DDDD3C0329CA741B109EF6CD6430BEDD00008AAD213DF25683D77F", "regionCode": "PT", "postalCode": "1229-076" } } ] }, "userProperties": { "customerType": "RETURNING" }, "eventName": "purchase", "clientId": "9876543210.1761582117", "userId": "user_DEF9876", "additionalEventParameters": [ { "parameterName": "ad_unit_name", "value": "Banner_02" } ], "cartData": { "transactionDiscount": 6.66, "items": [ { "itemId": "SKU_12346", "quantity": 2, "unitPrice": 21.01, "additionalItemParameters": [ { "parameterName": "item_name", "value": "Google Grey Women's Tee" }, { "parameterName": "affiliation", "value": "Google Merchandise Store" }, { "parameterName": "coupon", "value": "SUMMER_FUN" }, { "parameterName": "discount", "value": "3.33" }, { "parameterName": "index", "value": "1" }, { "parameterName": "item_brand", "value": "Google" }, { "parameterName": "item_category", "value": "Apparel" }, { "parameterName": "item_category2", "value": "Adult" }, { "parameterName": "item_category3", "value": "Shirts" }, { "parameterName": "item_category4", "value": "Crew" }, { "parameterName": "item_category5", "value": "Short sleeve" }, { "parameterName": "item_list_id", "value": "related_products" }, { "parameterName": "item_list_name", "value": "Related Products" } ] } ] } } ], "validateOnly": true }
資料合作夥伴
{ "destinations": [ { "operatingAccount": { "accountType": "OPERATING_ACCOUNT_TYPE", "accountId": "OPERATING_ACCOUNT_ID" }, "loginAccount": { "accountType": "DATA_PARTNER", "accountId": "DATA_PARTNER_ACCOUNT_ID" }, "linkedAccount": { "accountType": "LINKED_ACCOUNT_TYPE", "accountId": "LINKED_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}"); 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 #:%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/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),以及含有錯誤詳細資料的回應。
舉例來說,如果 emailAddress 包含純文字字串,而非十六進位編碼值,則會產生下列回應:
{
"error": {
"code": 400,
"message": "There was a problem with the request.",
"status": "INVALID_ARGUMENT",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "INVALID_ARGUMENT",
"domain": "datamanager.googleapis.com"
},
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"field": "events.events[0].user_data.user_identifiers",
"description": "Email is not hex encoded.",
"reason": "INVALID_HEX_ENCODING"
}
]
}
]
}
}
未經過雜湊處理且僅經過十六進位編碼的 emailAddress 會產生下列回應:
{
"error": {
"code": 400,
"message": "There was a problem with the request.",
"status": "INVALID_ARGUMENT",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "INVALID_ARGUMENT",
"domain": "datamanager.googleapis.com"
},
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"field": "events.events[0]",
"reason": "INVALID_SHA256_FORMAT"
}
]
}
]
}
}
將事件傳送至多個目的地
如果資料包含不同目的地的事件,您可以使用目的地參照,在同一個要求中傳送這些事件。如要瞭解每項要求可用的目的地數量上限,請參閱「限制和配額」。
舉例來說,如果您有轉換動作 ID 123456789 的事件,以及轉換動作 ID 777111122 的另一個事件,請設定每個 Destination 的 reference,在單一要求中傳送這兩個事件。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"
}
]
資料合作夥伴
"destinations": [
{
"operatingAccount": {
"accountType": "OPERATING_ACCOUNT_TYPE",
"accountId": "OPERATING_ACCOUNT_ID"
},
"loginAccount": {
"accountType": "DATA_PARTNER",
"accountId": "DATA_PARTNER_ACCOUNT_ID"
},
"linkedAccount": {
"accountType": "LINKED_ACCOUNT_TYPE",
"accountId": "LINKED_ACCOUNT_ID"
},
"productDestinationId": "123456789",
"reference": "destination_a"
},
{
"operatingAccount": {
"accountType": "OPERATING_ACCOUNT_2_TYPE",
"accountId": "OPERATING_ACCOUNT_2_ID"
},
"loginAccount": {
"accountType": "DATA_PARTNER",
"accountId": "DATA_PARTNER_ACCOUNT_2_ID"
},
"linkedAccount": {
"accountType": "LINKED_ACCOUNT_2_TYPE",
"accountId": "LINKED_ACCOUNT_2_ID"
},
"productDestinationId": "777111122",
"reference": "destination_b"
}
]
設定每個 Event 的 destinationReferences,將其傳送至一或多個特定目的地。舉例來說,以下 Event 只適用於第一個 Destination,因此其 destinationReferences 清單只包含第一個 Destination 的 reference:
{
"adIdentifiers": {
"gclid": "GCLID_1"
},
"conversionValue": 1.99,
"currency": "USD",
"eventTimestamp": "2025-06-10T20:07:01Z",
"transactionId": "ABC798654321",
"eventSource": "WEB",
"destinationReferences": [
"destination_a"
]
}
destinationReferences 欄位是清單,因此您可以為事件指定多個目的地。如未設定 Event 的 destinationReferences,Data Manager API 會將事件傳送至要求中的所有目的地。
如果事件有多個目的地,Data Manager API 會將相關欄位傳送至每個目的地。舉例來說,如果事件有 Google Ads 目的地和 Google Analytics 目的地,API 會在將事件傳送至 Google Analytics 目的地時,加入 clientId、appInstanceId 或 eventName 等 Google Analytics 欄位,並在將事件傳送至 Google Ads 目的地時,加入 customVariables 等 Google Ads 欄位。