برای آشنایی با فرآیند ارسال مخاطبان با استفاده از رابط برنامهنویسی کاربردی مدیریت داده (Data Manager API)، این راهنما را مطالعه کنید.
در این راهنما، مراحل زیر را انجام میدهید:
- یک
Destinationبرای دریافت دادههای مخاطبان آماده کنید. - دادههای مخاطبان را برای ارسال آماده کنید.
- یک درخواست
IngestionServiceبرای اعضای مخاطب ایجاد کنید. - درخواست را با Google APIs Explorer ارسال کنید.
- واکنشهای موفقیت و شکست را درک کنید.
آماده کردن یک مقصد
قبل از اینکه بتوانید داده ارسال کنید، باید مقصدی را که میخواهید داده به آن ارسال شود، آماده کنید. در اینجا یک نمونه Destination برای استفاده شما آورده شده است. برای مشاهده نمونههایی از مقصدها برای سناریوهای مختلف، به بخش پیکربندی مقصدها مراجعه کنید.
برگهای را انتخاب کنید که با مورد استفاده شما مطابقت دارد.
- اگر از اعتبارنامههای حساب گوگلی استفاده میکنید که کاربر آن در حسابهای تبلیغکنندهای است که میخواهید مدیریت کنید، تبلیغکننده را انتخاب کنید.
- اگر از اعتبارنامههای یک حساب گوگل استفاده میکنید که کاربری در یک حساب شریک داده است و میخواهید حسابهای تبلیغکنندهای را مدیریت کنید که یک لینک شریک به حساب شریک داده دارند، گزینه شریک داده را انتخاب کنید.
تبلیغ کننده
-
operatingAccount حسابی که دادههای مخاطبان را دریافت میکند. این را روی حساب تبلیغکنندهای که مالک مخاطبان است تنظیم کنید.
-
loginAccount حسابی که حساب گوگل برای اعتبارنامهها در آن یک کاربر است.
اگر اعتبارنامههای OAuth شما برای کاربری است که به حساب مدیریت تبلیغات گوگل (Google Ads manager account) دسترسی دارد، که
operatingAccountیکی از زیرحسابهای آن است،loginAccountروی نوع حساب و شناسه حساب مدیریت تنظیم کنید.اگر اعتبارنامههای OAuth برای کاربری با دسترسی مستقیم به
operatingAccountاست، نیازی به تنظیمloginAccountندارید.-
productDestinationId شناسهی مخاطب در حساب
operatingAccountکه دادههای عضو را دریافت میکند.
{
"operatingAccount": {
"accountType": "OPERATING_ACCOUNT_TYPE",
"accountId": "OPERATING_ACCOUNT_ID"
},
"loginAccount": {
"accountType": "LOGIN_ACCOUNT_TYPE",
"accountId": "LOGIN_ACCOUNT_ID"
},
"productDestinationId": "AUDIENCE_ID"
}
شریک داده
-
operatingAccount نوع حساب و شناسه حسابی که دادههای مخاطبان را دریافت میکند. این را روی حساب تبلیغکنندهای که مالک مخاطبان است تنظیم کنید.
-
loginAccount نوع حساب و شناسه حساب شریک داده شما.
-
linkedAccount حسابی با یک لینک همکاری ایجاد شده که از طریق آن کاربر دارای اعتبارنامه به
operatingAccountدسترسی دارد.اگر والد
operatingAccountبه حساب data partner شما متصل است،linkedAccountروی والدoperatingAccountتنظیم کنید. اگرoperatingAccountمستقیماً به حساب data partner شما متصل است،linkedAccountتنظیم نکنید.-
productDestinationId شناسهی مخاطب در حساب
operatingAccountکه دادههای عضو را دریافت میکند.
{
"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": "AUDIENCE_ID"
}
تهیه دادههای مخاطبان
دادههای نمونه زیر را در یک فایل جدا شده با کاما در نظر بگیرید. هر خط در فایل مربوط به یکی از اعضای مخاطب است و هر عضو حداکثر سه آدرس ایمیل دارد.
#,email_1,email_2,email_3
1,dana@example.com,DanaM@example.com,
2,ALEXJ@example.com, AlexJ@cymbalgroup.com,alexj@altostrat.com
3,quinn@CYMBALGROUP.com,baklavainthebalkans@gmail.com ,
4,rosario@example.org,cloudySanFrancisco@GMAIL.com,
آدرسهای ایمیل الزامات قالببندی و هشینگ زیر را دارند:
- تمام فضاهای خالی اول، آخر و وسط را حذف کنید.
- آدرس ایمیل را به حروف کوچک تبدیل کنید.
- آدرس ایمیل را با استفاده از الگوریتم SHA-256 هش کنید.
- بایتهای هش را با استفاده از کدگذاری هگزادسیمال (hexadecimal) یا Base64 کدگذاری کنید. مثالهای این راهنما از کدگذاری هگز استفاده میکنند.
دادههای فرمتشده به این صورت هستند:
#,email_1,email_2,email_3
1,dana@example.com,danam@example.com,
2,alexj@example.com,alexj@cymbalgroup.com,alexj@altostrat.com
3,quinn@cymbalgroup.com,baklavainthebalkans@gmail.com,
4,rosario@example.org,cloudysanfrancisco@gmail.com,
و این هم دادهها پس از هش و کدگذاری:
#,email_1,email_2,email_3
1,07e2f1394b0ea80e2adca010ea8318df697001a005ba7452720edda4b0ce57b3,1df6b43bc68dd38eca94e6a65b4f466ae537b796c81a526918b40ac4a7b906c7
2,2ef46c4214c3fc1b277a2d976d55194e12b899aa50d721f28da858c7689756e3,54e410b14fa652a4b49b43aff6eaf92ad680d4d1e5e62ed71b86cd3188385a51,e8bd3f8da6f5af73bec1ab3fbf7beb47482c4766dfdfc94e6bd89e359c139478
3,05bb62526f091b45d20e243d194766cca8869137421047dc53fa4876d111a6f0,f1fcde379f31f4d446b76ee8f34860eca2288adc6b6d6c0fdc56d9eee75a2fa5
4,83a834cc5327bc4dee7c5408988040dc5813c7662611cd93b707aff72bf7d33f,223ebda6f6889b1494551ba902d9d381daf2f642bae055888e96343d53e9f9c4
در اینجا یک نمونه AudienceMember برای آدرسهای ایمیل فرمتشده، هششده و کدگذاریشدهی dana@example.com و danam@example.com از ردیف اول دادههای ورودی آورده شده است:
{
"compositeData": {
"userData": {
"userIdentifiers": [
{
"emailAddress": "07e2f1394b0ea80e2adca010ea8318df697001a005ba7452720edda4b0ce57b3"
},
{
"emailAddress": "1df6b43bc68dd38eca94e6a65b4f466ae537b796c81a526918b40ac4a7b906c7"
}
]
}
}
}
دادههای ترکیبی
دو روش برای ارائه شناسههای کاربر برای آپلود AudienceMember وجود دارد.
compositeData( توصیه شده )این رویکرد برای همه ادغامها توصیه میشود.
فیلد
compositeDataرا درAudienceMemberروی یک شیءCompositeDataتنظیم کنید، که دارای یک فیلدuserDataتو در تو است که شناسههای کاربر میتوانند در آن ارائه شوند. عملکرد فیلدuserDataدرcompositeDataمشابه عملکرد فیلدuserDataدرAudienceMemberاست.CompositeDataمیتواند شامل یکی از موارد زیر باشد:- فقط شناسههای کاربر :
userDataحداقل با یکUserIdentifierپر شده است. - فقط دادههای آدرس IP : برای دستورالعملهای مربوط به پر کردن
ipData(پشتیبانی شده برای Google Ads Customer Match) به بخش IP ingestion for Google Ads Customer Match مراجعه کنید. - هم شناسههای کاربر و هم دادههای آدرس IP :
userDataوipDataهر دو با مقادیر معتبر پر شدهاند. برای دستورالعملهای مربوط به پر کردنipData(پشتیبانی شده برای Google Ads Customer Match) به بخش IP ingestion for Google Ads Customer Match مراجعه کنید.
- فقط شناسههای کاربر :
همچنین میتوانید با تنظیم مستقیم فیلد
userDataدرAudienceMemberروی یک شیءUserDataشناسههای کاربر را ارائه دهید.اگرچه
userDataهنوز پشتیبانی میشود، اما توصیه میکنیم ازcompositeDataاستفاده کنید تا مطمئن شوید که ادغام شما برای بهبودهای آینده آماده است.
دریافت IP برای تطابق مشتری تبلیغات گوگل
رابط برنامهنویسی کاربردی مدیریت داده (Data Manager API) از گنجاندن آدرسهای IP برای آپلودهای تطابق مشتری تبلیغات گوگل در CompositeData پشتیبانی میکند. برای هر آدرس IP، یک منبع IpData به فیلد تکراری ipData اضافه کنید. ارسال IpData برای آپلودهای تطابق مشتری Display & Video 360 پشتیبانی نمیشود.
IpData شامل یک فیلد ipAddress اجباری و دو فیلد timestamp اختیاری observeStartTime و observeEndTime است که نشان دهنده زمان مشاهده آدرس IP برای کاربر است. توصیه میکنیم این فیلدهای timestamp را برای کمک به بهبود نرخ تطابق مشتری اضافه کنید.
در اینجا دستورالعملهایی برای گنجاندن آدرسهای IP آمده است:
- هر دو IPv4 و IPv6 پشتیبانی میشوند.
- این فیلدها را هش یا کدگذاری نکنید.
- برای جزئیات بیشتر در مورد نحوه قالببندی فیلدهای
ipAddressو timestamp، به راهنمای قالببندی مراجعه کنید. - هر شیء در لیست
ipDataباید حاوی یک مقدار غیر خالی برای فیلدipAddressباشد. توصیه میکنیمobserveStartTimeوobserveEndTimeنیز ارسال کنید. - شما میتوانید با پر کردن
userDataوipDataدرCompositeData، شناسههای کاربری و دادههای آدرس IP را در آپلودهای Customer Match گوگل ادز خود ارسال کنید. مطمئن شوید کهuserDataحداقل یکUserIdentifierدارد. - مخاطب مقصد باید در
uploadKeyTypesخود دارایCONTACT_IDباشد. درخواستهای ارسالcompositeDataبه مخاطبانی که این نوع کلید را ندارند، رد خواهند شد.
در اینجا نمونه AudienceMember از ردیف اول دادههای ورودی آمده است که نحوه گنجاندن userData و ipData به صورت تو در تو در فیلد compositeData را نشان میدهد.
{
"compositeData": {
"userData": {
"userIdentifiers": [
{
"emailAddress": "07e2f1394b0ea80e2adca010ea8318df697001a005ba7452720edda4b0ce57b3"
},
{
"emailAddress": "1df6b43bc68dd38eca94e6a65b4f466ae537b796c81a526918b40ac4a7b906c7"
}
]
},
"ipData": [
{
"ipAddress": "192.0.2.1",
"observeStartTime": "2025-06-10T23:42:33Z",
"observeEndTime": "2025-06-10T23:43:59Z"
}
]
}
}
ساخت بدنه درخواست
برای ساخت بدنه درخواست، destinations و audienceMembers را با هم ترکیب کنید، فیلد encoding را تنظیم کنید و هر فیلد درخواست دیگری که میخواهید شامل شود، مانند validateOnly و consent اضافه کنید.
اگر مخاطبان را برای Customer Match ارسال میکنید، termsOfService طوری تنظیم کنید که نشان دهد آیا کاربر شرایط خدمات Customer Match را پذیرفته است یا خیر.
مثالهای این راهنما از رمزگذاری استفاده نمیکنند، اما میتوانید دستورالعملهای موجود در بخش «رمزگذاری دادههای کاربر» را برای افزودن رمزگذاری به فرآیند خود دنبال کنید.
درخواست را ارسال کنید
در اینجا مراحل امتحان کردن یک درخواست از مرورگر شما آمده است:
- تب REST را انتخاب کنید و روی Open in API Explorer کلیک کنید تا API Explorer در یک تب یا پنجره جدید باز شود.
- در بدنه درخواست در API Explorer، هر رشتهای که با
REPLACE_WITHشروع میشود، مانندREPLACE_WITH_OPERATING_ACCOUNT_TYPEرا با مقدار مربوطه جایگزین کنید. - روی Execute در پایین صفحه API Explorer کلیک کنید و مراحل احراز هویت را برای ارسال درخواست تکمیل کنید.
- برای اعتبارسنجی درخواست بدون اعمال تغییرات،
validateOnlyرا رویtrueتنظیم کنید. وقتی آماده اعمال تغییرات شدید،validateOnlyرا رویfalseتنظیم کنید.
اگر یک کتابخانه کلاینت نصب کردهاید ، برای مشاهده نمونه کد کامل نحوه ساخت و ارسال یک درخواست، زبان برنامهنویسی انتخابی خود را انتخاب کنید.
استراحت
تبلیغ کننده
{ "destinations": [ { "operatingAccount": { "accountType": "OPERATING_ACCOUNT_TYPE", "accountId": "OPERATING_ACCOUNT_ID" }, "loginAccount": { "accountType": "LOGIN_ACCOUNT_TYPE", "accountId": "LOGIN_ACCOUNT_ID" }, "productDestinationId": "AUDIENCE_ID" } ], "audienceMembers": [ { "compositeData": { "userData": { "userIdentifiers": [ { "emailAddress": "07e2f1394b0ea80e2adca010ea8318df697001a005ba7452720edda4b0ce57b3" }, { "emailAddress": "1df6b43bc68dd38eca94e6a65b4f466ae537b796c81a526918b40ac4a7b906c7" } ] } } }, { "compositeData": { "userData": { "userIdentifiers": [ { "emailAddress": "2ef46c4214c3fc1b277a2d976d55194e12b899aa50d721f28da858c7689756e3" }, { "emailAddress": "54e410b14fa652a4b49b43aff6eaf92ad680d4d1e5e62ed71b86cd3188385a51" }, { "emailAddress": "e8bd3f8da6f5af73bec1ab3fbf7beb47482c4766dfdfc94e6bd89e359c139478" } ] } } }, { "compositeData": { "userData": { "userIdentifiers": [ { "emailAddress": "05bb62526f091b45d20e243d194766cca8869137421047dc53fa4876d111a6f0" }, { "emailAddress": "f1fcde379f31f4d446b76ee8f34860eca2288adc6b6d6c0fdc56d9eee75a2fa5" } ] } } }, { "compositeData": { "userData": { "userIdentifiers": [ { "emailAddress": "83a834cc5327bc4dee7c5408988040dc5813c7662611cd93b707aff72bf7d33f" }, { "emailAddress": "223ebda6f6889b1494551ba902d9d381daf2f642bae055888e96343d53e9f9c4" } ] } } } ], "consent": { "adUserData": "CONSENT_GRANTED", "adPersonalization": "CONSENT_GRANTED" }, "encoding": "HEX", "termsOfService": { "customerMatchTermsOfServiceStatus": "ACCEPTED" }, "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": "AUDIENCE_ID" } ], "audienceMembers": [ { "compositeData": { "userData": { "userIdentifiers": [ { "emailAddress": "07e2f1394b0ea80e2adca010ea8318df697001a005ba7452720edda4b0ce57b3" }, { "emailAddress": "1df6b43bc68dd38eca94e6a65b4f466ae537b796c81a526918b40ac4a7b906c7" } ] } } }, { "compositeData": { "userData": { "userIdentifiers": [ { "emailAddress": "2ef46c4214c3fc1b277a2d976d55194e12b899aa50d721f28da858c7689756e3" }, { "emailAddress": "54e410b14fa652a4b49b43aff6eaf92ad680d4d1e5e62ed71b86cd3188385a51" }, { "emailAddress": "e8bd3f8da6f5af73bec1ab3fbf7beb47482c4766dfdfc94e6bd89e359c139478" } ] } } }, { "compositeData": { "userData": { "userIdentifiers": [ { "emailAddress": "05bb62526f091b45d20e243d194766cca8869137421047dc53fa4876d111a6f0" }, { "emailAddress": "f1fcde379f31f4d446b76ee8f34860eca2288adc6b6d6c0fdc56d9eee75a2fa5" } ] } } }, { "compositeData": { "userData": { "userIdentifiers": [ { "emailAddress": "83a834cc5327bc4dee7c5408988040dc5813c7662611cd93b707aff72bf7d33f" }, { "emailAddress": "223ebda6f6889b1494551ba902d9d381daf2f642bae055888e96343d53e9f9c4" } ] } } } ], "consent": { "adUserData": "CONSENT_GRANTED", "adPersonalization": "CONSENT_GRANTED" }, "encoding": "HEX", "termsOfService": { "customerMatchTermsOfServiceStatus": "ACCEPTED" }, "validateOnly": true }
دات نت
// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System.Text.Json; using CommandLine; using Google.Ads.DataManager.Util; using Google.Ads.DataManager.V1; using Google.Protobuf.WellKnownTypes; using static Google.Ads.DataManager.V1.ProductAccount.Types; namespace Google.Ads.DataManager.Samples { // <summary> // Sends a <see cref="IngestAudienceMembersRequest" /> without using encryption. // // User data is read from a data file. See the <c>audience_members_1.json</c> file in the // <c>sampledata</c> directory for an example. // </summary> public class IngestAudienceMembers { private static readonly int MaxMembersPerRequest = 10_000; [Verb( "ingest-audience-members", HelpText = "Sends an IngestAudienceMembersRequest 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( "linkedAccountType", 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("audienceId", Required = true, HelpText = "ID of the audience")] public string AudienceId { 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.AudienceId, options.JsonFile, options.ValidateOnly ); } // Runs the example. private void RunExample( AccountType operatingAccountType, string operatingAccountId, AccountType? loginAccountType, string? loginAccountId, AccountType? linkedAccountType, string? linkedAccountId, string audienceId, 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 the audience members from the JSON file. List<Member> memberList = ReadMemberData(jsonFile); // Creates a factory that will be used to generate the appropriate data manager. var userDataFormatter = new UserDataFormatter(); var audienceMembers = new List<AudienceMember>(); // Processes each batch of audience members. foreach (var member in memberList) { var compositeData = new CompositeData(); var userDataBuilder = new UserData(); // Adds a UserIdentifier for each valid email address for the member. foreach (var email in member.Emails) { try { string processedEmail = userDataFormatter.ProcessEmailAddress( email, UserDataFormatter.Encoding.Hex ); // Sets the email address identifier to the encoded hash. userDataBuilder.UserIdentifiers.Add( new UserIdentifier { EmailAddress = processedEmail } ); } catch (ArgumentException) { // Skips invalid input. continue; } } // Adds a UserIdentifier for each valid phone number for the member. foreach (var phoneNumber in member.PhoneNumbers) { try { string processedPhoneNumber = userDataFormatter.ProcessPhoneNumber( phoneNumber, UserDataFormatter.Encoding.Hex ); // Sets the phone number identifier to the encoded hash. userDataBuilder.UserIdentifiers.Add( new UserIdentifier { PhoneNumber = processedPhoneNumber } ); } catch (ArgumentException) { // Skips invalid input. continue; } } // Adds IP address information for each valid entry for the member. var ipDatas = new List<IpData>(); foreach (var ipInfo in member.IpInfos) { if (operatingAccountType != AccountType.GoogleAds) { Console.WriteLine( $"Skipping IP address information for operating account type {operatingAccountType}. " + "Sending IP address is only supported for operating account type GOOGLE_ADS." ); continue; } string processedIpAddress = (ipInfo.IpAddress ?? "").Trim(); if (string.IsNullOrEmpty(processedIpAddress)) { Console.WriteLine("Skipping IP address information with no IP address"); continue; } var ipData = new IpData { IpAddress = processedIpAddress }; string startTimeString = (ipInfo.ObserveStartTime ?? "").Trim(); if (!string.IsNullOrEmpty(startTimeString)) { try { ipData.ObserveStartTime = Timestamp.FromDateTime( DateTime.Parse(startTimeString).ToUniversalTime() ); } catch (FormatException) { Console.WriteLine( $"Ignoring observe start time '{startTimeString}' since it can't be parsed" ); } } string endTimeString = (ipInfo.ObserveEndTime ?? "").Trim(); if (!string.IsNullOrEmpty(endTimeString)) { try { ipData.ObserveEndTime = Timestamp.FromDateTime( DateTime.Parse(endTimeString).ToUniversalTime() ); } catch (FormatException) { Console.WriteLine( $"Ignoring observe end time '{endTimeString}' since it can't be parsed" ); } } ipDatas.Add(ipData); } if (userDataBuilder.UserIdentifiers.Any() || ipDatas.Any()) { if (userDataBuilder.UserIdentifiers.Any()) { compositeData.UserData = userDataBuilder; } if (ipDatas.Any()) { compositeData.IpData.AddRange(ipDatas); } audienceMembers.Add(new AudienceMember { CompositeData = compositeData }); } } // Builds the Destination for the request. var destinationBuilder = new Destination { // The destination account for the data. OperatingAccount = new ProductAccount { AccountType = operatingAccountType, AccountId = operatingAccountId, }, // The ID of the user list that is being updated. ProductDestinationId = audienceId, }; 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 audience members per request. for (var i = 0; i < audienceMembers.Count; i += MaxMembersPerRequest) { IEnumerable<AudienceMember> membersBatch = audienceMembers .Skip(i) .Take(MaxMembersPerRequest); requestCount++; // Builds the request. var request = new IngestAudienceMembersRequest { Destinations = { destinationBuilder }, // Adds members from the current batch. AudienceMembers = { membersBatch }, 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, TermsOfService = new TermsOfService { CustomerMatchTermsOfServiceStatus = TermsOfServiceStatus.Accepted, }, }; // Logs the request. Console.WriteLine($"Request #{requestCount}:\n{request}"); // Sends the data to the Data Manager API. IngestAudienceMembersResponse response = ingestionServiceClient.IngestAudienceMembers(request); Console.WriteLine($"Response for request #{requestCount}:\n{response}"); } Console.WriteLine($"# of requests sent: {requestCount}"); } private class Member { public List<string> Emails { get; set; } = new List<string>(); public List<string> PhoneNumbers { get; set; } = new List<string>(); public List<IpInfo> IpInfos { get; set; } = new List<IpInfo>(); public class IpInfo { public string? IpAddress { get; set; } public string? ObserveStartTime { get; set; } public string? ObserveEndTime { get; set; } } } private List<Member> ReadMemberData(string jsonFile) { string jsonString = File.ReadAllText(jsonFile); var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; return JsonSerializer.Deserialize<List<Member>>(jsonString, options) ?? new List<Member>(); } } }
جاوا
// 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.Encrypter; import com.google.ads.datamanager.util.UserDataFormatter; import com.google.ads.datamanager.util.UserDataFormatter.Encoding; import com.google.ads.datamanager.v1.AudienceMember; import com.google.ads.datamanager.v1.CompositeData; 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.EncryptionInfo; import com.google.ads.datamanager.v1.GcpWrappedKeyInfo; import com.google.ads.datamanager.v1.GcpWrappedKeyInfo.KeyType; import com.google.ads.datamanager.v1.IngestAudienceMembersRequest; import com.google.ads.datamanager.v1.IngestAudienceMembersResponse; import com.google.ads.datamanager.v1.IngestionServiceClient; import com.google.ads.datamanager.v1.IpData; import com.google.ads.datamanager.v1.ProductAccount; import com.google.ads.datamanager.v1.ProductAccount.AccountType; import com.google.ads.datamanager.v1.TermsOfService; import com.google.ads.datamanager.v1.TermsOfServiceStatus; 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.security.GeneralSecurityException; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * Sends an {@link IngestAudienceMembersRequest} with the option to use encryption. * * <p>User data is read from a data file. See the {@code audience_members_1.json} file in the {@code * resources/sampledata} directory for a sample file. */ public class IngestAudienceMembers { private static final Logger LOGGER = Logger.getLogger(IngestAudienceMembers.class.getName()); /** The maximum number of audience members allowed per request. */ private static final int MAX_MEMBERS_PER_REQUEST = 10_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 = "--audienceId", required = true, description = "ID of the audience") String audienceId; @Parameter( names = "--jsonFile", required = true, description = "JSON file containing user data to ingest") String jsonFile; @Parameter( names = "--keyUri", required = false, description = "URI of the Google Cloud KMS key for encrypting data. If this parameter is set, you" + " must also set the --wipProvider parameter.") String keyUri; @Parameter( names = "--wipProvider", required = false, description = "Workload Identity Pool provider name for encrypting data. If this parameter is set," + " you must also set the --keyUri parameter. The argument for this parameter must" + " follow the pattern:" + " projects/PROJECT_ID/locations/global/workloadIdentityPools/WIP_ID/providers/PROVIDER_ID") String wipProvider; @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, GeneralSecurityException { 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"); } if ((paramsConfig.keyUri == null) != (paramsConfig.wipProvider == null)) { throw new IllegalArgumentException( "Must specify either both or neither of key URI and WIP provider"); } new IngestAudienceMembers().runExample(paramsConfig); } /** * Runs the example. * * @param params the parameters for the example */ private void runExample(ParamsConfig params) throws IOException, GeneralSecurityException { // Reads member data from the data file. List<Member> memberList = readMemberData(params.jsonFile); // Gets an instance of the UserDataFormatter for normalizing and formatting the data. UserDataFormatter userDataFormatter = UserDataFormatter.create(); // Determines if encryption parameters are set. boolean useEncryption = (params.keyUri != null && params.wipProvider != null); Encrypter encrypter = null; if (useEncryption) { // Gets an instance of the encryption utility. encrypter = Encrypter.createForGcpKms(params.keyUri, null); } // Builds the audience_members collection for the request. List<AudienceMember> audienceMembers = new ArrayList<>(); for (Member member : memberList) { // Creates a builder for CompositeData to add user data and/or IP address information. CompositeData.Builder compositeDataBuilder = CompositeData.newBuilder(); // Creates a builder for user data for hashed emails and phone numbers. UserData.Builder userDataBuilder = compositeDataBuilder.getUserDataBuilder(); // Adds a UserIdentifier for each valid email address for the member. for (String email : member.emails) { String processedEmail; try { processedEmail = useEncryption ? userDataFormatter.processEmailAddress(email, Encoding.HEX, encrypter) : userDataFormatter.processEmailAddress(email, Encoding.HEX); } catch (IllegalArgumentException iae) { // Skips invalid input. continue; } // Sets the email address identifier to the encoded and possibly encrypted email hash. userDataBuilder.addUserIdentifiers( UserIdentifier.newBuilder().setEmailAddress(processedEmail)); } // Adds a UserIdentifier for each valid phone number for the member. for (String phoneNumber : member.phoneNumbers) { String processedPhoneNumber; try { processedPhoneNumber = useEncryption ? userDataFormatter.processPhoneNumber(phoneNumber, Encoding.HEX, encrypter) : userDataFormatter.processPhoneNumber(phoneNumber, Encoding.HEX); } catch (IllegalArgumentException iae) { // Skips invalid input. continue; } // Sets the phone number identifier to the encoded and possibly encrypted phone number // hash. userDataBuilder.addUserIdentifiers( UserIdentifier.newBuilder().setPhoneNumber(processedPhoneNumber)); } // Adds IP address information for each valid entry for the member. for (Member.IpInfo ipInfo : member.ipInfos) { if (!AccountType.GOOGLE_ADS.equals(params.operatingAccountType)) { LOGGER.log( Level.INFO, "Skipping IP address information for operating account type {0}. " + "Sending IP address is only supported for operating account type {1}.", new Object[] {params.operatingAccountType, AccountType.GOOGLE_ADS}); } // Trims leading and trailing whitespace from the IP address. String processedIpAddress = Strings.nullToEmpty(ipInfo.ipAddress).trim(); if (processedIpAddress.isEmpty()) { // Skips the IP information from the input file since it is missing IP address, which is // required. LOGGER.info("Skipping IP address information with no IP address"); continue; } // Creates an IpData builder and sets its IP address. IpData.Builder ipDataBuilder = compositeDataBuilder.addIpDataBuilder().setIpAddress(processedIpAddress); // Adds observe start time to the IpData if present and in the expected timestamp format. String startTimeString = Strings.nullToEmpty(ipInfo.observeStartTime).trim(); if (!startTimeString.isEmpty()) { try { ipDataBuilder.setObserveStartTime(Timestamps.parse(startTimeString)); } catch (ParseException e) { LOGGER.log( Level.INFO, "Ignoring observe start time '{0} since it can't be parsed", startTimeString); } } // Adds observe end time to the IpData if present and in the expected timestamp format. String endTimeString = Strings.nullToEmpty(ipInfo.observeEndTime).trim(); if (!endTimeString.isEmpty()) { try { ipDataBuilder.setObserveEndTime(Timestamps.parse(endTimeString)); } catch (ParseException e) { LOGGER.log( Level.INFO, "Ignoring observe end time '{0}' since it can't be parsed", endTimeString); } } } if (userDataBuilder.getUserIdentifiersCount() > 0 || compositeDataBuilder.getIpDataCount() > 0) { // Adds the CompositeData containing user data, IP address information, or both. audienceMembers.add( AudienceMember.newBuilder().setCompositeData(compositeDataBuilder).build()); } } // Builds the Destination for the request. Destination.Builder destinationBuilder = Destination.newBuilder() .setOperatingAccount( ProductAccount.newBuilder() .setAccountType(params.operatingAccountType) .setAccountId(params.operatingAccountId)) .setProductDestinationId(params.audienceId); 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)); } // Configures the EncryptionInfo for the request if encryption parameters provided. EncryptionInfo encryptionInfo = null; if (useEncryption) { encryptionInfo = EncryptionInfo.newBuilder() .setGcpWrappedKeyInfo( GcpWrappedKeyInfo.newBuilder() .setKekUri(params.keyUri) .setWipProvider(params.wipProvider) .setKeyType(KeyType.XCHACHA20_POLY1305) // Sets the encrypted_dek field to the Base64-encoded encrypted DEK. .setEncryptedDek( userDataFormatter.base64Encode( encrypter.getEncryptedDek().toByteArray()))) .build(); } try (IngestionServiceClient ingestionServiceClient = IngestionServiceClient.create()) { int requestCount = 0; // Batches requests to send up to the maximum number of audience members per request. for (List<AudienceMember> audienceMembersBatch : Lists.partition(audienceMembers, MAX_MEMBERS_PER_REQUEST)) { requestCount++; // Builds the request. IngestAudienceMembersRequest.Builder requestBuilder = IngestAudienceMembersRequest.newBuilder() .addDestinations(destinationBuilder) // Adds members from the current batch. .addAllAudienceMembers(audienceMembersBatch) .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) .setTermsOfService( TermsOfService.newBuilder() .setCustomerMatchTermsOfServiceStatus(TermsOfServiceStatus.ACCEPTED)); if (useEncryption) { // Sets encryption info on the request. requestBuilder.setEncryptionInfo(encryptionInfo); } // Builds and logs the request. IngestAudienceMembersRequest request = requestBuilder.build(); if (LOGGER.isLoggable(Level.INFO)) { LOGGER.info(String.format("Request #%d:%n%s", requestCount, request)); } // Sends the request. IngestAudienceMembersResponse response = ingestionServiceClient.ingestAudienceMembers(request); // Logs the response. if (LOGGER.isLoggable(Level.INFO)) { LOGGER.info(String.format("Response for request #%d:%n%s", requestCount, response)); } } LOGGER.info("# of requests sent: " + requestCount); } } /** Data object for a single row of input data. */ @SuppressWarnings("unused") private static class Member { private List<String> emails = new ArrayList<>(); private List<String> phoneNumbers = new ArrayList<>(); private List<IpInfo> ipInfos = new ArrayList<>(); private static class IpInfo { private String ipAddress; private String observeStartTime; private String observeEndTime; } } /** * Reads the data file and parses it into a list of {@link IngestAudienceMembers.Member} objects. * * @param jsonFile the JSON data file * @return a list of Member objects */ private List<Member> readMemberData(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 Member objects) Type recordListType = new TypeToken<ArrayList<Member>>() {}.getType(); // Parse the JSON string from the file into a List of Member 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 { AudienceMember, CompositeData, Destination, Encoding: DataManagerEncoding, Consent, ConsentStatus, IngestAudienceMembersRequest, IpData, ProductAccount, TermsOfService, TermsOfServiceStatus, 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_MEMBERS_PER_REQUEST = 10000; interface Arguments { operating_account_type: string; operating_account_id: string; audience_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 MemberRow { emails?: string[]; phoneNumbers?: string[]; ipInfos?: { ipAddress: string; observeStartTime?: string; observeEndTime?: string; }[]; } /** * The main function for the IngestAudienceMembers 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('audience_id', { describe: 'The ID of the destination audience.', 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(); const formatter = new UserDataFormatter(); const memberRows: MemberRow[] = await readMemberData(argv.json_file); // Converts the operating account type argument to the corresponding enum // so IP address checks can compare enums to enums. const operatingAccountType = convertToAccountType( argv.operating_account_type, 'operating_account_type', ); // Builds the audience_members collection for the request. const audienceMembers = []; for (const memberRow of memberRows) { const userData = UserData.create(); // Adds a UserIdentifier for each valid email address for the member. for (const email of memberRow.emails || []) { try { const processedEmail = formatter.processEmailAddress( email, Encoding.HEX, ); userData.userIdentifiers.push( UserIdentifier.create({emailAddress: processedEmail}), ); } catch (e) { // Skip invalid input. } } // Adds a UserIdentifier for each valid phone number for the member. for (const phone of memberRow.phoneNumbers || []) { try { const processedPhone = formatter.processPhoneNumber( phone, Encoding.HEX, ); userData.userIdentifiers.push( UserIdentifier.create({phoneNumber: processedPhone}), ); } catch (e) { // Skip invalid input. } } // Process IP address information const ipDatas = []; for (const ipInfo of memberRow.ipInfos || []) { if (operatingAccountType !== ProductAccount.AccountType.GOOGLE_ADS) { console.log( `Skipping IP address information for operating account type ${operatingAccountType}. ` + 'Sending IP address is only supported for operating account type GOOGLE_ADS.', ); continue; } const ipAddress = (ipInfo.ipAddress || '').trim(); if (!ipAddress) { console.log('Skipping IP address information with no IP address'); continue; } const ipData = IpData.create({ipAddress}); if (ipInfo.observeStartTime) { const startTimeStr = ipInfo.observeStartTime.trim(); if (startTimeStr) { try { const date = new Date(startTimeStr); if (isNaN(date.getTime())) { throw new Error('Invalid date'); } ipData.observeStartTime = { seconds: Math.floor(date.getTime() / 1000), nanos: (date.getTime() % 1000) * 1e6, }; } catch (e) { console.log( `Ignoring observe start time '${startTimeStr}' since it can't be parsed`, ); } } } if (ipInfo.observeEndTime) { const endTimeStr = ipInfo.observeEndTime.trim(); if (endTimeStr) { try { const date = new Date(endTimeStr); if (isNaN(date.getTime())) { throw new Error('Invalid date'); } ipData.observeEndTime = { seconds: Math.floor(date.getTime() / 1000), nanos: (date.getTime() % 1000) * 1e6, }; } catch (e) { console.log( `Ignoring observe end time '${endTimeStr}' since it can't be parsed`, ); } } } ipDatas.push(ipData); } if (userData.userIdentifiers.length > 0 || ipDatas.length > 0) { const compositeData = CompositeData.create(); if (userData.userIdentifiers.length > 0) { compositeData.userData = userData; } if (ipDatas.length > 0) { compositeData.ipData = ipDatas; } audienceMembers.push(AudienceMember.create({compositeData})); } else { console.warn('Ignoring line. No data.'); } } const destination = Destination.create({ operatingAccount: ProductAccount.create({ accountType: operatingAccountType, accountId: argv.operating_account_id, }), productDestinationId: argv.audience_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 audience members per request. for (let i = 0; i < audienceMembers.length; i += MAX_MEMBERS_PER_REQUEST) { requestCount++; const audienceMembersBatch = audienceMembers.slice( i, i + MAX_MEMBERS_PER_REQUEST, ); const request = IngestAudienceMembersRequest.create({ destinations: [destination], // Adds members from the current batch. audienceMembers: audienceMembersBatch, consent: Consent.create({ adUserData: ConsentStatus.CONSENT_GRANTED, adPersonalization: ConsentStatus.CONSENT_GRANTED, }), termsOfService: TermsOfService.create({ customerMatchTermsOfServiceStatus: TermsOfServiceStatus.ACCEPTED, }), // Sets encoding to match the encoding used. encoding: DataManagerEncoding.HEX, // Sets validate_only. If true, then the Data Manager API only validates the request // but doesn't apply changes. validateOnly: argv.validate_only, }); console.log(`Request #${requestCount}:\n `, request); const [response] = await client.ingestAudienceMembers(request); console.log(`Response for request #${requestCount}:\n `, response); } console.log(`# of requests sent: ${requestCount}`); } /** * Reads the user data from the given JSON file. * @param {string} jsonFile The path to the JSON file. * @return {Promise<MemberRow[]>} A promise that resolves with an array of user data. */ async function readMemberData(jsonFile: string): Promise<MemberRow[]> { const data = await fs.promises.readFile(jsonFile, 'utf8'); return JSON.parse(data) as MemberRow[]; } /** * 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 // 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 IngestAudienceMembersRequest without encryption. */ require_once dirname(__DIR__, 1) . '/vendor/autoload.php'; use Google\Ads\DataManager\V1\AudienceMember; use Google\Ads\DataManager\V1\Client\IngestionServiceClient; use Google\Ads\DataManager\V1\CompositeData; 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\IngestAudienceMembersRequest; use Google\Ads\DataManager\V1\IpData; use Google\Ads\DataManager\V1\ProductAccount; use Google\Ads\DataManager\V1\ProductAccount\AccountType; use Google\Ads\DataManager\V1\TermsOfService; use Google\Ads\DataManager\V1\TermsOfServiceStatus; 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; /** * Reads the JSON member data file. * * @param string $jsonFile The member data file. Expected format is a JSON array of * objects, where each object can contain "emails" and "phoneNumbers" arrays. * @return array A list of associative arrays, each representing a member. */ function readMemberData(string $jsonFile): array { $content = file_get_contents($jsonFile); if ($content === false) { throw new \RuntimeException(sprintf('Could not read JSON file: %s', $jsonFile)); } $data = json_decode($content, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new \RuntimeException(sprintf('Error decoding JSON: %s', json_last_error_msg())); } return $data; } /** * Runs the sample. * * @param int $operatingAccountType The account type of the operating account. * @param string $operatingAccountId The ID of the operating account. * @param string $audienceId The ID of the destination audience. * @param string $jsonFile The JSON file containing member 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 $audienceId, string $jsonFile, bool $validateOnly, ?int $loginAccountType = null, ?string $loginAccountId = null, ?int $linkedAccountType = null, ?string $linkedAccountId = null ): void { // Reads member data from the data file. $memberRows = readMemberData($jsonFile); // Gets an instance of the UserDataFormatter for normalizing and formatting the data. $formatter = new Formatter(); // Builds the audience_members collection for the request. $audienceMembers = []; foreach ($memberRows as $memberRow) { $identifiers = []; // Adds a UserIdentifier for each valid email address for the member. $emails = $memberRow['emails'] ?? []; foreach ($emails as $email) { try { // Formats, hashes, and encodes the email address. $processedEmail = $formatter->processEmailAddress($email, Encoding::Hex); // Sets the email address identifier to the encoded email hash. $identifiers[] = (new UserIdentifier())->setEmailAddress($processedEmail); } catch (\InvalidArgumentException $e) { // Skips invalid input. error_log(sprintf('Skipping invalid email: %s', $e->getMessage())); continue; } } // Adds a UserIdentifier for each valid phone number for the member. $phoneNumbers = $memberRow['phoneNumbers'] ?? []; foreach ($phoneNumbers as $phone) { try { // Formats, hashes, and encodes the phone number. $processedPhone = $formatter->processPhoneNumber($phone, Encoding::Hex); // Sets the phone number identifier to the encoded phone number hash. $identifiers[] = (new UserIdentifier())->setPhoneNumber($processedPhone); } catch (\InvalidArgumentException $e) { // Skips invalid input. error_log(sprintf('Skipping invalid phone: %s', $e->getMessage())); continue; } } // Adds IP address information for each valid entry for the member. $ipDatas = []; $ipInfos = $memberRow['ipInfos'] ?? []; foreach ($ipInfos as $ipInfo) { if ($operatingAccountType !== AccountType::GOOGLE_ADS) { error_log(sprintf( 'Skipping IP address information for operating account type %s. ' . 'Sending IP address is only supported for operating account type GOOGLE_ADS.', AccountType::name($operatingAccountType) )); continue; } // Trims leading and trailing whitespace from the IP address. $ipAddress = trim($ipInfo['ipAddress'] ?? ''); if (empty($ipAddress)) { // Skips the IP information from the input file since it is missing IP address, which is // required. error_log('Skipping IP address information with no IP address'); continue; } // Creates an IpData message and sets its IP address. $ipData = (new IpData())->setIpAddress($ipAddress); // Adds observe start time to the IpData if present and in the expected timestamp format. $startTimeStr = trim($ipInfo['observeStartTime'] ?? ''); if (!empty($startTimeStr)) { try { $startTime = new Timestamp(); $startTime->fromDateTime(new \DateTime($startTimeStr)); $ipData->setObserveStartTime($startTime); } catch (\Exception $e) { error_log(sprintf("Ignoring observe start time '%s' since it can't be parsed", $startTimeStr)); } } // Adds observe end time to the IpData if present and in the expected timestamp format. $endTimeStr = trim($ipInfo['observeEndTime'] ?? ''); if (!empty($endTimeStr)) { try { $endTime = new Timestamp(); $endTime->fromDateTime(new \DateTime($endTimeStr)); $ipData->setObserveEndTime($endTime); } catch (\Exception $e) { error_log(sprintf("Ignoring observe end time '%s' since it can't be parsed", $endTimeStr)); } } $ipDatas[] = $ipData; } if (!empty($identifiers) || !empty($ipDatas)) { $compositeData = new CompositeData(); if (!empty($identifiers)) { $userData = (new UserData())->setUserIdentifiers($identifiers); $compositeData->setUserData($userData); } if (!empty($ipDatas)) { $compositeData->setIpData($ipDatas); } // Adds an AudienceMember with the CompositeData. $audienceMember = (new AudienceMember())->setCompositeData($compositeData); $audienceMembers[] = $audienceMember; } } // Builds the destination for the request. $destination = new Destination(); $destination->setOperatingAccount(new ProductAccount() ->setAccountType($operatingAccountType) ->setAccountId($operatingAccountId)); 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)); } $destination->setProductDestinationId($audienceId); // Builds the request. $request = (new IngestAudienceMembersRequest()) ->setDestinations([$destination]) ->setAudienceMembers($audienceMembers) ->setConsent((new Consent()) ->setAdUserData(ConsentStatus::CONSENT_GRANTED) ->setAdPersonalization(ConsentStatus::CONSENT_GRANTED) ) ->setTermsOfService((new TermsOfService()) ->setCustomerMatchTermsOfServiceStatus(TermsOfServiceStatus::ACCEPTED) ) // Sets encoding to match the encoding used. ->setEncoding(DataManagerEncoding::HEX) // Sets validate_only to true to validate but not apply the changes in the request. ->setValidateOnly(true); // Creates a client for the ingestion service. $client = new IngestionServiceClient(); try { // Logs the request. echo "Request:\n" . json_encode(json_decode($request->serializeToJsonString()), JSON_PRETTY_PRINT) . "\n"; // Sends the request. $response = $client->ingestAudienceMembers($request); echo "Response:\n" . json_encode(json_decode($response->serializeToJsonString()), JSON_PRETTY_PRINT) . "\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::', 'audience_id:', 'json_file:', 'validate_only::' ] ); $operatingAccountType = $options['operating_account_type'] ?? null; $operatingAccountId = $options['operating_account_id'] ?? null; $audienceId = $options['audience_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($audienceId) || empty($jsonFile)) { echo 'Usage: php ingest_audience_members.php ' . '--operating_account_type=<account_type> ' . '--operating_account_id=<account_id> ' . '--audience_id=<audience_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, $audienceId, $jsonFile, $validateOnly, $parsedLoginAccountType, $options['login_account_id'] ?? null, $parsedLinkedAccountType, $options['linked_account_id'] ?? null );
پایتون
#!/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 IngestAudienceMembersRequest with the option to use 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 Encrypter 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 audience members allowed per request. _MAX_MEMBERS_PER_REQUEST = 10_000 def main( operating_account_type: str, operating_account_id: str, audience_id: str, json_file: str, validate_only: bool, login_account_type: Optional[str] = None, login_account_id: Optional[str] = None, linked_account_type: Optional[str] = None, linked_account_id: Optional[str] = None, key_uri: str = None, wip_provider: 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. audience_id: the ID of the destination audience. json_file: the JSON file containing member 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. key_uri: the URI of the Google Cloud KMS key. wip_provider: the Workload Identity Pool provider name. Must follow the pattern: projects/PROJECT_ID/locations/global/workloadIdentityPools/WIP_ID/providers/PROVIDER_ID """ # Validates parameter pairs. 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" ) 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" ) if bool(key_uri) != bool(wip_provider): raise ValueError( "Must specify either both or neither of key URI and WIP provider" ) # Gets an instance of the formatter. formatter: Formatter = Formatter() # Determines if encryption parameters are set. use_encryption: bool = key_uri and wip_provider encrypter: Encrypter = None if use_encryption: # Creates an instance of the encryption utility. encrypter = Encrypter.create_for_gcp_kms(key_uri) # Reads the input file. member_rows: List[Dict[str, Any]] = read_member_data(json_file) audience_members: List[datamanager_v1.AudienceMember] = [] member_row: Dict[str, Any] for member_row in member_rows: composite_data = datamanager_v1.CompositeData() user_data = datamanager_v1.UserData() email: str for email in member_row.get("emails", []): try: processed_email: str = formatter.process_email_address( email, Encoding.HEX, encrypter ) user_data.user_identifiers.append( datamanager_v1.UserIdentifier(email_address=processed_email) ) except ValueError: # Skips invalid input. continue phone: str for phone in member_row.get("phoneNumbers", []): try: processed_phone: str = formatter.process_phone_number( phone, Encoding.HEX, encrypter ) user_data.user_identifiers.append( datamanager_v1.UserIdentifier(phone_number=processed_phone) ) except ValueError: # Skips invalid input. continue for ip_info in member_row.get("ipInfos", []): if ( operating_account_type != datamanager_v1.ProductAccount.AccountType.GOOGLE_ADS.name ): _logger.info( "Skipping IP address information for operating account type %s. " "Sending IP address is only supported for operating account type GOOGLE_ADS.", operating_account_type, ) continue ip_address = ip_info.get("ipAddress", "").strip() if not ip_address: _logger.info( "Skipping IP address information with no IP address" ) continue ip_data = datamanager_v1.IpData(ip_address=ip_address) start_time_str = ip_info.get("observeStartTime", "").strip() if start_time_str: try: start_time = Timestamp() start_time.FromJsonString(start_time_str) ip_data.observe_start_time = start_time except ValueError: _logger.info( "Ignoring observe start time '%s' since it can't be parsed", start_time_str, ) end_time_str = ip_info.get("observeEndTime", "").strip() if end_time_str: try: end_time = Timestamp() end_time.FromJsonString(end_time_str) ip_data.observe_end_time = end_time except ValueError: _logger.info( "Ignoring observe end time '%s' since it can't be parsed", end_time_str, ) composite_data.ip_data.append(ip_data) if user_data.user_identifiers or composite_data.ip_data: if user_data.user_identifiers: composite_data.user_data = user_data audience_member = datamanager_v1.AudienceMember( composite_data=composite_data ) audience_members.append(audience_member) # 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 if login_account_type or 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: destination.linked_account.account_type = linked_account_type destination.linked_account.account_id = linked_account_id destination.product_destination_id = audience_id # Configures the EncryptionInfo for the request if encryption parameters provided. if use_encryption: encryption_info: datamanager_v1.EncryptionInfo = ( datamanager_v1.EncryptionInfo( gcp_wrapped_key_info=datamanager_v1.GcpWrappedKeyInfo( kek_uri=key_uri, wip_provider=wip_provider, key_type=datamanager_v1.GcpWrappedKeyInfo.KeyType.XCHACHA20_POLY1305, # Sets the encrypted_dek field to the Base64-encoded encrypted DEK. encrypted_dek=formatter.base64_encode( encrypter.encrypted_dek_bytes ), ) ) ) # Creates a client for the ingestion service. client: datamanager_v1.IngestionServiceClient = ( datamanager_v1.IngestionServiceClient() ) # Batches requests to send up to the maximum number of audience members per # request. request_count = 0 for i in range(0, len(audience_members), _MAX_MEMBERS_PER_REQUEST): request_count += 1 audience_members_batch = audience_members[ i : i + _MAX_MEMBERS_PER_REQUEST ] # Sends the request. request: datamanager_v1.IngestAudienceMembersRequest = ( datamanager_v1.IngestAudienceMembersRequest( destinations=[destination], # Adds members from the current batch. audience_members=audience_members_batch, consent=datamanager_v1.Consent( ad_user_data=datamanager_v1.ConsentStatus.CONSENT_GRANTED, ad_personalization=datamanager_v1.ConsentStatus.CONSENT_GRANTED, ), terms_of_service=datamanager_v1.TermsOfService( customer_match_terms_of_service_status=datamanager_v1.TermsOfServiceStatus.ACCEPTED, ), # 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, ) ) if use_encryption: # Sets encryption info on the request. request.encryption_info = encryption_info # Logs the request. _logger.info("Request #%d:\n%s", request_count, request) # Sends the request. response: datamanager_v1.IngestAudienceMembersResponse = ( client.ingest_audience_members(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_member_data(json_file: str) -> List[Dict[str, Any]]: """Reads the JSON member data file. Args: json_file: the member data file. Expected format is a JSON array of objects, where each object can contain "emails" and "phoneNumbers" arrays. """ 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 audience members from a CSV 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( "--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( "--audience_id", type=str, required=True, help="The ID of the destination audience.", ) parser.add_argument( "--json_file", type=str, required=True, help="JSON file containing user data to ingest.", ) parser.add_argument( "--key_uri", type=str, required=False, help="URI of the Google Cloud KMS key for encrypting data. If this parameter is set, you " + "must also set the --wip_provider parameter.", ) parser.add_argument( "--wip_provider", type=str, required=False, help="Workload Identity Pool provider name for encrypting data. If this parameter is set, " + "you must also set the --key_uri parameter. The argument for this parameter must follow " + "the pattern: " + "projects/PROJECT_ID/locations/global/workloadIdentityPools/WIP_ID/providers/PROVIDER_ID", ) 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.audience_id, args.json_file, args.validate_only == "true", args.login_account_type, args.login_account_id, args.linked_account_type, args.linked_account_id, args.key_uri, args.wip_provider, )
پاسخهای موفقیت
یک درخواست موفق، پاسخی را با یک شیء حاوی requestId برمیگرداند.
{
"requestId": "126365e1-16d0-4c81-9de9-f362711e250a"
}
requestId برگردانده شده را ثبت کنید تا بتوانید با پردازش هر مقصد در درخواست، عیبیابی را بازیابی کنید.
پاسخهای مربوط به شکست
یک درخواست ناموفق منجر به یک کد وضعیت پاسخ خطا مانند 400 Bad Request و پاسخی با جزئیات خطا میشود.
برای مثال، یک emailAddress که حاوی یک رشته متن ساده به جای یک مقدار کدگذاری شده هگز است، پاسخ زیر را تولید میکند:
{
"error": {
"code": 400,
"message": "There was a problem with the request.",
"status": "INVALID_ARGUMENT",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "INVALID_ARGUMENT",
"domain": "datamanager.googleapis.com"
},
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"field": "audience_members.audience_members[0].composite_data.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": "audience_members.audience_members[0].composite_data.user_data",
"reason": "INVALID_SHA256_FORMAT"
}
]
}
]
}
}
ارسال رویدادها برای چندین مقصد
اگر دادههای شما شامل مخاطبانی با مقاصد مختلف است، میتوانید با استفاده از ارجاعات مقصد، آنها را در یک درخواست واحد ارسال کنید. برای اطلاع از حداکثر تعداد مقاصد در هر درخواست، به بخش محدودیتها و سهمیهها مراجعه کنید.
برای مثال، اگر یک مخاطب با شناسه لیست کاربری 11112222 و یک مخاطب دیگر با شناسه لیست کاربری 77778888 دارید، هر دو مخاطب را در یک درخواست واحد با تنظیم 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": "11112222",
"reference": "audience_1"
},
{
"operatingAccount": {
"accountType": "OPERATING_ACCOUNT_2_TYPE",
"accountId": "OPERATING_ACCOUNT_2_ID"
},
"loginAccount": {
"accountType": "LOGIN_ACCOUNT_2_TYPE",
"accountId": "LOGIN_ACCOUNT_2_ID"
},
"productDestinationId": "77778888",
"reference": "audience_2"
}
]
شریک داده
"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": "11112222",
"reference": "audience_1"
},
{
"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": "77778888",
"reference": "audience_2"
}
]
destination_references هر AudienceMember را طوری تنظیم کنید که آن را به یک یا چند مقصد خاص ارسال کند. برای مثال، در اینجا یک AudienceMember وجود دارد که فقط برای اولین Destination است، بنابراین لیست destination_references آن فقط شامل reference اولین Destination است:
{
"compositeData": {
"userData": {
"userIdentifiers": [
{
"emailAddress": "07e2f1394b0ea80e2adca010ea8318df697001a005ba7452720edda4b0ce57b3"
},
{
"emailAddress": "1df6b43bc68dd38eca94e6a65b4f466ae537b796c81a526918b40ac4a7b906c7"
}
]
}
},
"destinationReferences": [
"audience_1"
]
}
فیلد destination_references یک لیست است، بنابراین میتوانید چندین مقصد را برای یک عضو مخاطب مشخص کنید. اگر destination_references یک AudienceMember را تنظیم نکنید، رابط برنامهنویسی کاربردی (API) مدیر داده، عضو مخاطب را به تمام مقاصد موجود در درخواست ارسال میکند.
مراحل بعدی
- احراز هویت را پیکربندی کنید و محیط خود را با یک کتابخانه کلاینت راهاندازی کنید.
- در مورد الزامات قالببندی، هش کردن و کدگذاری برای هر نوع داده اطلاعات کسب کنید.
- آموزش رمزگذاری دادههای کاربران
- یاد بگیرید چگونه برای درخواستهایتان ، تشخیصهای لازم را بازیابی کنید .
- درباره بهترین شیوهها اطلاعات کسب کنید.
- درباره محدودیتها و سهمیهها اطلاعات کسب کنید.