המדריך למפתחים של Attribution Reporting API

כשתקראו את מסמכי התיעוד של ארגז החול לפרטיות ב-Android, השתמשו בלחצן תצוגה מקדימה למפתחים או בטא כדי לבחור את גרסת התוכנית שאיתה אתם עובדים, מאחר שההוראות עשויות להשתנות.


שליחת משוב

Attribution Reporting API נועד לבטל את ההסתמכות על מזהי משתמשים של צדדים שונים במטרה לשפר את פרטיות המשתמשים, ולספק תמיכה בתרחישים חשובים של שימוש בשיוך ובמדידה של המרות באפליקציות שונות. במדריך למפתחים מוסבר איך להגדיר ולבדוק את ממשקי ה-API של דוחות השיוך (Attribution) כדי לרשום קליקים, צפיות והמרות על המודעות, באמצעות קריאה לשיטות שרושמות את הטריגרים והמקורות הרלוונטיים לאירועים כאלה.

במדריך הזה תלמדו איך להגדיר נקודות קצה לשרת ולבנות אפליקציית לקוח שמפעילה את השירות הזה. מידע נוסף על העיצוב הכולל של Attribution Reporting API זמין בהצעת העיצוב.

מונחי מפתח

  • מקורות שיוך (Attribution) מתייחסים לקליקים או לצפיות.
  • טריגרים הם אירועים שאפשר לשייך להמרות.
  • דוחות כוללים נתונים על טריגר ועל מקור השיוך (Attribution) המתאים. הדוחות האלה נשלחים בתגובה להפעלת אירועים. ב-Attribution Reporting API יש תמיכה בדוחות ברמת האירוע ובדוחות נצברים.

לפני שמתחילים

כדי להשתמש ב-Attribution Reporting API, צריך לבצע את המשימות בצד השרת ובצד הלקוח שמפורטות בקטעים הבאים.

הגדרת נקודות הקצה של Attribution Reporting API

כדי להשתמש ב-Attribution Reporting API נדרשת קבוצה של נקודות קצה שניתן לגשת אליהן ממכשיר בדיקה או מאמולטור. צרו נקודת קצה אחת לכל אחת מהמשימות הבאות בצד השרת:

יש כמה שיטות להגדיר את נקודות הקצה הנדרשות:

  • הדרך המהירה ביותר להתחיל לפעול היא לפרוס את הגדרות השירות OpenAPI v3 ממאגר הקוד לדוגמה שלנו לפלטפורמה מדומה או מיקרו-שירותים. אפשר להשתמש ב-Postman, ב-Prism או בכל פלטפורמה אחרת של שרת מדומה שמקבלת את הפורמט הזה. כדאי לפרוס כל נקודת קצה ולעקוב אחרי מזהי ה-URI לשימוש באפליקציה. כדי לוודא את מסירת הדיווח, כדאי לעיין בקריאות שבוצעו בעבר לפלטפורמה מדומה או לפלטפורמה ללא שרת.
  • תוכלו להריץ שרת עצמאי משלכם באמצעות דוגמת Kotlin שמבוססת על Spring Boot. פורסים את השרת הזה אצל ספק שירותי הענן או בתשתית הפנימית.
  • הגדרות השירות משמשות כדוגמאות לשילוב נקודות הקצה במערכת הקיימת.

אישור רישום המקור

צריך לאפשר גישה לנקודת הקצה הזו מ-URI שדומה לזה:

https://adtech.example/attribution_source

כשאפליקציית לקוח רושמת מקור שיוך, היא מספקת את ה-URI של נקודת הקצה הזו של השרת. לאחר מכן, ה-Attribution Reporting API שולח בקשה וכולל אחת מהכותרות הבאות:

  • לגבי אירועי קליק:

    Attribution-Reporting-Source-Info: navigation
    
  • לצפייה באירועים:

    Attribution-Reporting-Source-Info: event
    

מגדירים את נקודת הקצה של השרת כך שיגיבו באמצעות:

// Metadata associated with attribution source.
Attribution-Reporting-Register-Source: {
  "destination": "[app package name]",
  "web_destination": "[eTLD+1]",
  "source_event_id": "[64 bit unsigned integer]",
  "expiry": "[64 bit signed integer]",
  "event_report_window": "[64-bit signed integer]",
  "aggregatable_report_window": "[64-bit signed integer]",
  "priority": "[64 bit signed integer]",
  "filter_data": {
    "[key name 1]": ["key1 value 1", "key1 value 2"],
    "[key name 2]": ["key2 value 1", "key2 value 2"],
    // Note: "source_type" key will be automatically generated as
    // one of {"navigation", "event"}.
  },
  // Attribution source metadata specifying histogram contributions in aggregate
  // report.
  "aggregation_keys": {
    "[key1 name]": "[key1 value]",
    "[key2 name]": "[key2 value]",
  },

    "debug_key": "[64-bit unsigned integer]",
    "debug_reporting": [boolean]
}
// Specify additional ad tech URLs to register this source with.
Attribution-Reporting-Redirect: <Ad Tech Partner URI 1>
Attribution-Reporting-Redirect: <Ad Tech Partner URI 2>

הנה דוגמה לערכים לדוגמה שנוספו:

Attribution-Reporting-Register-Source: {
  "destination": "android-app://com.example.advertiser",
  "source_event_id": "234",
  "expiry": "259200",
  "event_report_window": "172800",
  "aggregatable_report_window": "172800",
  "priority": "5",
  "filter_data": {
    "product_id": ["1234"]
  },
  "aggregation_keys": {
  // Generates a "0x159" key piece named (low order bits of the key) for the key
  // named "campaignCounts".
  // User saw an ad from campaign 345 (out of 511).
    "campaignCounts": "0x159",

  // Generates a "0x5" key piece (low order bits of the key) for the key named
  // "geoValue".
  // Source-side geo region = 5 (US), out of a possible ~100 regions.
    "geoValue": "0x5",
  },
  // Opts in to receiving verbose debug reports
  "debug_reporting": true
}

Attribution-Reporting-Redirect:
https://adtechpartner1.example?their_ad_click_id=567
Attribution-Reporting-Redirect:
https://adtechpartner2.example?their_ad_click_id=890

אם Attribution-Reporting-Redirects מכיל מזהי URI של שותפי פרסום דיגיטלי, Attribution Reporting API ישלח בקשה דומה לכל URI. כל שותף פרסום דיגיטלי צריך להגדיר שרת שמגיב באמצעות הכותרות הבאות:

Attribution-Reporting-Register-Source: {
  "destination": "[app package name]",
  "web_destination": "[eTLD+1]",
  "source_event_id": "[64 bit unsigned integer]",
  "expiry": "[64 bit signed integer]",
  "event_report_window": "[64-bit signed integer]",
  "aggregatable_report_window": "[64-bit signed integer]",
  "priority": "[64 bit signed integer]",
  "filter_data": {
    "[key name 1]": ["key1 value 1", "key1 value 2"],
    "[key name 2]": ["key2 value 1", "key2 value 2"],
    // Note: "source_type" key will be automatically generated as
    // one of {"navigation", "event"}.
  },
  "aggregation_keys": {
    "[key1 name]": "[key1 value]",
    "[key2 name]": "[key2 value]",
  }
}
// The Attribution-Reporting-Redirect header is ignored for ad tech partners.

אישור רישום של טריגר המרה

צריך לאפשר גישה לנקודת הקצה הזו מ-URI שדומה לזה:

https://adtech.example/attribution_trigger

כשאפליקציית לקוח רושמת אירוע של טריגר, היא מספקת את ה-URI של נקודת הקצה הזו של השרת. לאחר מכן, ה-Attribution Reporting API שולח בקשה וכולל אחת מהכותרות הבאות:

מגדירים את נקודת הקצה של השרת כך שיגיבו באמצעות:

// Metadata associated with trigger.
Attribution-Reporting-Register-Trigger: {
  "event_trigger_data": [{
    // "trigger_data returned" in event reports is truncated to
    // the last 1 or 3 bits, based on conversion type.
    "trigger_data": "[unsigned 64-bit integer]",
    "priority": "[signed 64-bit integer]",
    "deduplication_key": "[signed 64-bit integer]",
    // "filter" and "not_filters" are optional fields which allow configuring
    // event trigger data based on source's filter_data. They consist of a
    // filter set, which is a list of filter maps. An event_trigger_data object
    // is ignored if none of the filter maps in the set match the source's
    // filter data.
    // Note: "source_type" can be used as a key in a filter map to filter based
    // on the source's "navigation" or "event" type. The first
    // Event-Trigger that matches (based on the filters/not_filters) will be
    // used for report generation. If none of the event-triggers match, no
    // event report will be generated.
    "filters": [{
      "[key name 1]": ["key1 value 1", "key1 value 2"],
      // If a key is missing from filters or source's filter_data, it won't be
      // used during matching.
      "[key name 2]": ["key2 value 1", "key2 value 2"],
    }],
    "not_filters":  [{
      "[key name 1]": ["key1 value 1", "key1 value 2"],
      // If a key is missing from not_filters or source's filter_data, it won't
      // be used during matching.
      "[key name 2]": ["key2 value 1", "key2 value 2"],
    }]
  }],
  // Specify a list of dictionaries that generates aggregation keys.
  "aggregatable_trigger_data": [
    // Each dictionary entry independently adds pieces to multiple source keys.
    {
      "key_piece": "[key piece value]",
      "source_keys": ["[key name the key piece value applies to]",
      ["list of IDs in source to match. Non-matching IDs are ignored"]]
      // filters/not_filters are optional fields similar to event trigger data
      // filter fields.
      "filters": [{
        "[key name 1]": ["key1 value 1", "key1 value 2"]
      }],
      "not_filters":  [{
          "[key name 1]": ["key1 value 1", "key1 value 2"],
          "[key name 2]": ["key2 value 1", "key2 value 2"],
      }]
    },
    ..
  ],
  // Specify an amount of an abstract value which can be integers in [1, 2^16]
  // to contribute to each key that is attached to aggregation keys in the
  // order they are generated.
  "aggregatable_values": [
     // Each source event can contribute a maximum of L1 = 2^16 to the
     // aggregate histogram.
    {
     "[key_name]": [value]
    },
    ..
  ],
  aggregatable_deduplication_keys: [{
  deduplication_key": [unsigned 64-bit integer],
    "filters": {
        "category": [filter_1, …, filter_H]
      },
    "not_filters": {
        "category": [filter_1, …, filter_J]
      }
  },
  ...
  {
  "deduplication_key": [unsigned 64-bit integer],
    "filters": {
        "category": [filter_1, …, filter_D]
      },
    "not_filters": {
        "category": [filter_1, …, filter_J]
      }
    }
  ]

  "debug_key": "[64-bit unsigned integer]",
  "debug_reporting": [boolean]

}
// Specify additional ad tech URLs to register this trigger with.
// Repeated Header field "Attribution-Reporting-Redirect"
Attribution-Reporting-Redirect: <Ad Tech Partner URI 1>
Attribution-Reporting-Redirect: <Ad Tech Partner URI 2>

הנה דוגמה לערכים לדוגמה שנוספו:

Attribution-Reporting-Register-Trigger: {
  "event_trigger_data": [{
    "trigger_data": "1122", // Returns 010 for CTCs and 0 for VTCs in reports.
    "priority": "3",
    "deduplication_key": "3344"
    "filters": [{ // Filter strings can not exceed 25 characters
      "product_id": ["1234"],
      "source_type": ["event"]
    }]
  },
  {
    "trigger_data": "4", // Returns 100 for CTCs and 0 for VTCs in reports.
    "priority": "3",
    "deduplication_key": "3344"
    "filters": [{ // Filter strings can not exceed 25 characters
      "product_id": ["1234"],
      "source_type": ["navigation"]
    }]
  }],
  "aggregatable_trigger_data": [
    // Each dictionary independently adds pieces to multiple source keys.
    {
      // Conversion type purchase = 2 at a 9-bit offset, i.e. 2 << 9.
      // A 9-bit offset is needed because there are 511 possible campaigns,
      // which takes up 9 bits in the resulting key.
      "key_piece": "0x400",// Conversion type purchase = 2
      // Apply this key piece to:
      "source_keys": ["campaignCounts"]
       // Filter strings can not exceed 25 characters
    },
    {
      // Purchase category shirts = 21 at a 7-bit offset, i.e. 21 << 7.
      // A 7-bit offset is needed because there are ~100 regions for the geo
      // key, which takes up 7 bits of space in the resulting key.
      "key_piece": "0xA80",
      // Apply this key piece to:
      "source_keys": ["geoValue", "nonMatchingIdsAreIgnored"]
      // source_key values must not exceed the limit of 25 characters
    }
  ],
  "aggregatable_values":
    {
      // Privacy budget for each key is L1 / 2 = 2^15 (32768).
      // Conversion count was 1.
      // Scale the count to use the full budget allocated: 1 * 32768 = 32768.
      "campaignCounts": 32768,

      // Purchase price was $52.
      // Purchase values for the app range from $1 to $1,024 (integers only).
      // Scaling factor applied is 32768 / 1024 = 32.
      // For $52 purchase, scale the value by 32 ($52 * 32 = $1,664).
      "geoValue": 1664
    }
  ,
  // aggregatable_deduplication_keys is an optional field. Up to 50 "keys"
  // can be included in the aggregatable_deduplication_keys list. Filters, not
  // filters, and deduplication_key are optional fields. If deduplication_key
  // is omitted, it will be treated as a null value. See
  // https://wicg.github.io/attribution-reporting-api/#triggering-aggregatable-attribution
  aggregatable_deduplication_keys:
  [
    {
    deduplication_key": 3,
        "filters": {
          "category": [A]
        }
    },
    {
    "deduplication_key": 4,
        "filters": {
          "category": [C, D]
        },
        "not_filters": {
          "category": [F]
        }
    }
  ]
  // Opts into receiving verbose debug reports
  "debug_reporting": true
}
Attribution-Reporting-Redirect:https://adtechpartner.example?app_install=567

קיימת מגבלה של 25 בייטים לכל מזהה של מפתח צבירה ולמחרוזת מסנן. כלומר, המזהים של מפתח הצבירה ומחרוזות הסינון לא יכולים לחרוג מ-25 תווים. בדוגמה הזו, הערך של campaignCounts הוא 14 תווים, כך שהוא מזהה מפתח צבירה תקין, ו-1234 הוא 4 תווים, כך שזו מחרוזת חוקית של מסנן. אם מזהה מפתח צבירה או מחרוזת מסנן חורגים מ-25 תווים, המערכת תתעלם מהטריגר.

אם Attribution-Reporting-Redirect מכיל מזהי URI של שותפי פרסום דיגיטלי, Attribution Reporting API ישלח בקשה דומה לכל URI. כל שותף פרסום דיגיטלי צריך להגדיר שרת שמגיב באמצעות הכותרות הבאות:

// Metadata associated with trigger.
Attribution-Reporting-Register-Trigger: {
  "event_trigger_data": [{
    // "trigger_data" returned in event reports is truncated to
    // the last 1 or 3 bits, based on conversion type.
    "trigger_data": "[unsigned 64-bit integer]",
    "priority": "[signed 64-bit integer]",
    "deduplication_key": "[signed 64-bit integer]",
    // filter and not_filters are optional fields which allow configuring
    // different event trigger data based on source's filter_data. They
    // consist of a filter set, which is a list of filter maps. An
    // event_trigger_data object is ignored if none of the filter maps in the
    // set match the source's filter data. Note: "source_type" can be used as
    // a key in a filter map to filter based on the source's "navigation" or
    // "event" type. The first Event-Trigger that matches (based on the
    // filters/not_filters) will be used for report generation. If none of the
    // event-triggers match, no report will be generated.
    "filters": [{
      "[key name 1]": ["key1 value 1", "key1 value 2"],
      // If a key is missing from filters or source's filter_data, it will not be
      // used during matching.
      "[key name 2]": ["key2 value 1", "key2 value 2"],
    }],
    "not_filters":  [{
      "[key name 1]": ["key1 value 1", "key1 value 2"],
      // If a key is missing from not_filters or source's filter_data, it will not
      // be used during matching.
      "[key name 2]": ["key2 value 1", "key2 value 2"],
    }]
  }],
  "aggregatable_trigger_data": [
    // Each dictionary entry independently adds pieces to multiple source keys.
    {
      "key_piece": "[key piece value]",
      "source_keys": ["[key name the key piece value applies to]",
      ["list of IDs in source to match. Non-matching IDs are ignored"]],
      // filters/not_filters are optional fields similar to event trigger data
      // filter fields.
      "filters": [{
        "[key name 1]": ["key1 value 1", "key1 value 2"]
      }],
      "not_filters":  [{
          "[key name 1]": ["key1 value 1", "key1 value 2"],
          "[key name 2]": ["key2 value 1", "key2 value 2"],
      }]
    },
    ..
  ],
  // Specify an amount of an abstract value which can be integers in [1, 2^16] to
  // contribute to each key that is attached to aggregation keys in the order they
  // are generated.
  "aggregatable_values": [
    // Each source event can contribute a maximum of L1 = 2^16 to the aggregate
    // histogram.
    {
     "[key_name]": [value]
    }
  ]
}
// The Attribution-Reporting-Redirect header is ignored for ad tech partners.

אישור דוחות ברמת האירוע

נקודת הקצה הזו צריכה להיות ניתנת לגישה מ-URI. מידע נוסף על רישום מזהי URI זמין במאמר הרשמה לחשבון של ארגז חול לפרטיות. (המערכת מסיקה את ה-URI על סמך המקור של השרתים שמשמשים לאישור רישום מקור ולהפעלת רישום). ה-URI של נקודת הקצה הזו מתבסס על דוגמאות של מזהי URI לנקודות קצה שמאשרות רישום מקור ומאשרות הרשמה לטריגר, ולכן ה-URI של נקודת הקצה הזו:

https://adtech.example/.well-known/attribution-reporting/report-event-attribution

צריך להגדיר את השרת הזה לקבלת בקשות JSON בפורמט הבא:

{
  "attribution_destination": "android-app://com.advertiser.example",
  "source_event_id": "12345678",
  "trigger_data": "2",
  "report_id": "12324323",
  "source_type": "navigation",
  "randomized_trigger_rate": "0.02"
   [Optional] "source_debug_key": "[64-bit unsigned integer]",
   [Optional] "trigger_debug_key": "[64-bit unsigned integer]",
}

מפתחות ניפוי באגים מאפשרים לקבל תובנות נוספות לגבי דוחות השיוך (Attribution) שלכם. מידע נוסף על ההגדרה שלהם.

אישור של דוחות מצטברים

נקודת הקצה הזו צריכה להיות ניתנת לגישה מ-URI. מידע נוסף על רישום מזהי URI זמין במאמר הרשמה לחשבון של ארגז חול לפרטיות. (המערכת מסיקה את ה-URI על סמך המקור של השרתים שמשמשים לאישור רישום מקור ולהפעלת רישום). ה-URI של נקודת הקצה הזו מתבסס על דוגמאות של מזהי URI לנקודות קצה שמאשרות רישום מקור ומאשרות הרשמה לטריגר, ולכן ה-URI של נקודת הקצה הזו:

https://adtech.example/.well-known/attribution-reporting/report-aggregate-attribution

גם השדות המוצפנים וגם השדות הלא מוצפנים יאוכלסו בדוחות שניתן לצבור. באמצעות הדוחות המוצפנים תוכלו להתחיל את הבדיקה בשירות הצבירה, והשדה הלא מוצפן מספק תובנות לגבי הדרך שבה זוגות מפתח-ערך מוגדרים בונים את הנתונים.

צריך להגדיר את השרת הזה לקבלת בקשות JSON בפורמט הבא:

{
  // Info that the aggregation services also need encoded in JSON
  // for use with AEAD. Line breaks added for readability.
  "shared_info": "{
     \"api\":\"attribution-reporting\",
     \"attribution_destination\": \"android-app://com.advertiser.example.advertiser\",
     \"scheduled_report_time\":\"[timestamp in seconds]\",
     \"source_registration_time\": \"[timestamp in seconds]\",
     \"version\":\"[api version]\",
     \"report_id\":\"[UUID]\",
     \"reporting_origin\":\"https://reporter.example\" }",

  // In the current Developer Preview release, The "payload" and "key_id" fields
  // are not used because the platform does not yet encrypt aggregate reports.
  // Currently, the "debug_cleartext_payload" field holds unencrypted reports.
  "aggregation_service_payloads": [
    {
      "payload": "[base64 HPKE encrypted data readable only by the aggregation service]",
      "key_id": "[string identifying public key used to encrypt payload]",

      "debug_cleartext_payload": "[unencrypted payload]"
    },
  ],

  "source_debug_key": "[64 bit unsigned integer]",
  "trigger_debug_key": "[64 bit unsigned integer]"
}

מפתחות ניפוי באגים מאפשרים לקבל תובנות נוספות לגבי דוחות השיוך (Attribution) שלכם. מידע נוסף על ההגדרה שלהם.

הגדרת לקוח Android

אפליקציית הלקוח רושמת מקורות שיוך וטריגרים, ומאפשרת ליצור דוחות נצברים ברמת האירוע. כדי להכין אמולטור או מכשיר של לקוח Android לשימוש ב-Attribution Reporting API:

  1. מגדירים את סביבת הפיתוח לארגז החול לפרטיות ב-Android.
  2. מתקינים תמונת מערכת במכשיר נתמך או מגדירים אמולטור שכולל תמיכה בארגז החול לפרטיות ב-Android.
  3. מפעילים את הגישה ל-Attribution Reporting API על ידי הרצת הפקודה הבאה של ADB. (ה-API מושבת כברירת מחדל).

    adb shell device_config put adservices ppapi_app_allow_list \"\*\"
    
  4. אם אתם בודקים באופן מקומי את Attribution Reporting API (למשל בדיקה במכשיר שיש לכם גישה אליו פיזית), מריצים את הפקודה הבאה כדי להשבית את ההרשמה:

    adb shell device_config put adservices disable_measurement_enrollment_check "true"
    
  5. כוללים את ההרשאה ACCESS_ADSERVICES_ATTRIBUTION בקובץ המניפסט של Android ויוצרים הגדרה של שירותי מודעות לאפליקציה כדי להשתמש בממשקי Attribution Reporting API:

    <uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION" />
    
  6. (אופציונלי) אם מתכוונים לקבל דוחות על ניפוי באגים, צריך לכלול את ההרשאה ACCESS_ADSERVICES_AD_ID בקובץ המניפסט של Android:

    <uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" />
    
  7. יש להפנות להגדרה של שירותי מודעות ברכיב <application> של המניפסט:

    <property android:name="android.adservices.AD_SERVICES_CONFIG"
              android:resource="@xml/ad_services_config" />
    
  8. צריך לציין את משאב ה-XML של שירותי המודעות שיש אליו הפניה במניפסט, למשל res/xml/ad_services_config.xml. מידע נוסף על הרשאות לשירותי מודעות ובקרת גישה ל-SDK

    <ad-services-config>
        <attribution allowAllToAccess="true" />
    </ad-services-config>
    

רישום אירועי מודעות

האפליקציה צריכה לרשום מקורות והמרות בזמן שהם מתרחשים, כדי לוודא שהם מדווחים כראוי. MeasurementManager – שיטות שבעזרתן תוכלו לרשום אירועי שיוך (Attribution) וטריגרים של המרות.

רישום אירוע של מקור שיוך (Attribution)

כשצופים במודעה או לוחצים עליה, אפליקציה של בעל אפליקציה מפעילה registerSource() כדי לרשום מקור ייחוס כפי שמוצג בקטע הקוד.

Attribution Reporting API תומך בסוגים הבאים של אירועי מקור שיוך:

  • קליקים, שבדרך כלל מתועדים בשיטת קריאה חוזרת (callback) בדומה ל-onClick(). בדרך כלל, האירוע של הטריגר המתאים מתרחש זמן קצר אחרי האירוע של הקליק. אירועים מהסוג הזה מספקים מידע נוסף על אינטראקציות של משתמשים, ולכן הם סוג טוב של מקור ייחוס שאפשר להקצות לו עדיפות גבוהה.
  • צפיות, שבדרך כלל מתועדות בשיטת קריאה חוזרת (callback) בדומה ל-onAdShown(). אירוע הטריגר המתאים יכול להתרחש שעות או ימים אחרי אירוע הצפייה.

Kotlin

companion object {
    private val CALLBACK_EXECUTOR = Executors.newCachedThreadPool()
}

val measurementManager = context.getSystemService(MeasurementManager::class.java)
var exampleClickEvent: InputEvent? = null

// Use the URI of the server-side endpoint that accepts attribution source
// registration.
val attributionSourceUri: Uri =
  Uri.parse("https://adtech.example/attribution_source?AD_TECH_PROVIDED_METADATA")

val future = CompletableFuture<Void>()

adView.setOnTouchListener(_: View?, event: MotionEvent?)) ->
    exampleClickEvent = event
    true
}

// Register Click Event
measurementManager.registerSource(
        attributionSourceUri,
        exampleClickEvent,
        CALLBACK_EXECUTOR,
        future::complete)

// Register View Event
measurementManager.registerSource(
        attributionSourceUri,
        null,
        CALLBACK_EXECUTOR,
        future::complete)

Java

private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
private InputEvent exampleClickEvent;

MeasurementManager measurementManager =
        context.getSystemService(MeasurementManager.class);

// Use the URI of the server-side endpoint that accepts attribution source
// registration.
Uri attributionSourceUri =
Uri.parse("https://adtech.example/attribution_source?AD_TECH_PROVIDED_METADATA");

CompletableFuture<Void> future = new CompletableFuture<>();

adView.setOnTouchListener(v, event)) -> {
    exampleClickEvent = event;
    return true;
}

// Register Click Event
measurementManager.registerSource(attributionSourceUri, exampleClickEvent,
        CALLBACK_EXECUTOR, future::complete);

// Register View Event
measurementManager.registerSource(attributionSourceUri, null,
        CALLBACK_EXECUTOR, future::complete);

לאחר הרישום, ה-API מנפיק בקשת HTTP POST לנקודת הקצה של השירות, בכתובת שצוינה ב-attributionSourceUri. התגובה של נקודת הקצה כוללת ערכים של destination, source_event_id, expiry ושל source_priority.

אם טכנולוגיית המודעות המקורית רוצה לשתף את הרישומים של המקור, ה-URI המקורי של מקור השיוך יכול לכלול הפניות אוטומטיות לנקודות קצה אחרות של טכנולוגיות פרסום. המגבלות והכללים הרלוונטיים להפניות אוטומטיות מפורטים בהצעה הטכנית.

נוספה תמיכה בהפניות אוטומטיות מסוג 'שרשור' עבור registerSource ו-registerTrigger. בנוסף לכותרת הרישום, צרכן ה-API יכול לספק מעכשיו הפניה אוטומטית מסוג HTTP כתגובת השרת, שכוללת קוד סטטוס 302 וכותרת "Location" עם כתובת ה-URL הבאה שבה אמורים לבקר לצורך רישום נוסף.

רק שדה היעד שצוין בביקור הראשון משמש בכל הרשת. למספר הביקורים יש אותה מגבלה כמו הכותרות של "Attribution-Reporting-Redirect". התמיכה בהפניה אוטומטית קיימת בנוסף לתמיכה הקיימת של Attribution-Reporting-Redirect, ואם שתיהן קיימות, תינתן עדיפות ל-'Attribution-Reporting-Redirect'.

רישום אירוע של טריגר המרה

כדי לרשום אירוע של טריגר המרה, צריך להפעיל את השיטה registerTrigger() באפליקציה:

Kotlin

companion object {
    private val CALLBACK_EXECUTOR = Executors.newCachedThreadPool()
}

val measurementManager = context.getSystemService(MeasurementManager::class.java)

// Use the URI of the server-side endpoint that accepts trigger registration.
val attributionTriggerUri: Uri =
    Uri.parse("https://adtech.example/trigger?AD_TECH_PROVIDED_METADATA")

val future = CompletableFuture<Void>()

// Register trigger (conversion)
measurementManager.registerTrigger(
        attributionTriggerUri,
        CALLBACK_EXECUTOR,
        future::complete)

Java

private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();

MeasurementManager measurementManager =
        context.getSystemService(MeasurementManager.class);

// Use the URI of the server-side endpoint that accepts trigger registration.
Uri attributionTriggerUri =
        Uri.parse("https://adtech.example/trigger?AD_TECH_PROVIDED_METADATA");

CompletableFuture<Void> future = new CompletableFuture<>();

// Register trigger (conversion)
measurementManager.registerTrigger(
        attributionTriggerUri,
        CALLBACK_EXECUTOR,
        future::complete)

לאחר הרישום, ה-API מנפיק בקשת HTTP POST לנקודת הקצה של השירות, בכתובת שצוינה ב-attributionTriggerUri. התגובה של נקודת הקצה כוללת ערכים של דוחות נצברים ואירועים.

אם הפלטפורמה לטכנולוגיות הפרסום מאפשרת לשתף הרשמות של טריגרים, ה-URI יכול לכלול הפניות אוטומטיות למזהי URI ששייכים לפלטפורמות אחרות של פרסום דיגיטלי. המגבלות והכללים הרלוונטיים להפניות אוטומטיות מפורטים בהצעה הטכנית.

רישום מדידה באפליקציות ובאתרים שונים

במקרים שבהם גם האפליקציה וגם הדפדפן פועלים במסע של המשתמש מהמקור ועד ההפעלה, יש הבדלים קלים באופן ההטמעה של רישום אירועי המודעות. אם משתמש רואה מודעה באפליקציה ומופנה לדפדפן כדי לבצע המרה, המקור רשום באפליקציה וההמרה מתבצעת על ידי דפדפן האינטרנט. באותו אופן, אם משתמש מתחיל בדפדפן אינטרנט ומופנה לאפליקציה לצורך המרה, הדפדפן רושם את המקור והאפליקציה תתעד את ההמרה.

מכיוון שיש הבדלים באופן הארגון של טכנולוגיות הפרסום באינטרנט וב-Android, הוספנו ממשקי API חדשים כדי לרשום מקורות וטריגרים כשהם מיושמים בדפדפנים. ההבדל העיקרי בין ממשקי ה-API האלה לבין ממשקי ה-API המתאימים הוא בכך שאנחנו מצפים שהדפדפן יעקוב אחר ההפניות האוטומטיות, יחיל מסננים ספציפיים לדפדפן ויעביר את הרישומים התקפים לפלטפורמה על ידי קריאה ל-registerWebSource() או ל-registerWebTrigger().

קטע הקוד הבא מציג דוגמה לקריאה ל-API שמוגדרת בדפדפן כדי לרשום מקור שיוך לפני שהוא מפנה את המשתמש לאפליקציה:

Kotlin

companion object {
    private val CALLBACK_EXECUTOR = Executors.newCachedThreadPool()
}

val measurementManager =
        context.getSystemService(MeasurementManager::class.java)
var exampleClickEvent: InputEvent? = null

// Use the URIs of the server-side endpoints that accept attribution source
// registration.
val sourceParam1 = WebSourceParams.Builder(Uri.parse(
        "https://adtech1.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
// True, if debugging is allowed for the ad tech.
    .setDebugKeyAllowed(true)
    .build()

val sourceParam2 = WebSourceParams.Builder(Uri.parse(
        "https://adtech2.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    .setDebugKeyAllowed(false)
    .build()

val sourceParam3 = WebSourceParams.Builder(Uri.parse(
        "https://adtech3.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    .build()

val sourceParams = Arrays.asList(sourceParam1, sourceParam2, sourceParam3)
val publisherOrigin = Uri.parse("https://publisher.example")
val appDestination = Uri.parse("android-app://com.example.store")
val webDestination = Uri.parse("https://example.com")

val future = CompletableFuture<Void>()

adView.setOnTouchListener {_: View?, event: MotionEvent? ->
    exampleClickEvent = event
    true
}
val clickRegistrationRequest = WebSourceRegistrationRequest.Builder(
          sourceParams,
          publisherOrigin)
      .setAppDestination(appDestination)
      .setWebDestination(webDestination)
      .setInputEvent(event)
      .build()
val viewRegistrationRequest = WebSourceRegistrationRequest.Builder(
          sourceParams,
          publisherOrigin)
      .setAppDestination(appDestination)
      .setWebDestination(webDestination)
      .setInputEvent(null)
      .build()

// Register a web source for a click event.
measurementManager.registerWebSource(
        clickRegistrationRequest,
        CALLBACK_EXECUTOR,
        future::complete)

// Register a web source for a view event.
measurementManager.registerWebSource(
        viewRegistrationRequest,
        CALLBACK_EXECUTOR,
        future::complete)

Java

private static final Executor CALLBACK_EXECUTOR =
        Executors.newCachedThreadPool();
private InputEvent exampleClickEvent;

MeasurementManager measurementManager =
        context.getSystemService(MeasurementManager.class);

// Use the URIs of the server-side endpoints that accept attribution source
// registration.
WebSourceParams sourceParam1 = WebSourceParams.Builder(Uri.parse(
        "https://adtech1.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    // True, if debugging is allowed for the ad tech.
    .setDebugKeyAllowed(true)
    .build();

WebSourceParams sourceParam2 = WebSourceParams.Builder(Uri.parse(
        "https://adtech2.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    .setDebugKeyAllowed(false)
    .build();

WebSourceParams sourceParam3 = WebSourceParams.Builder(Uri.parse(
        "https://adtech3.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    .build();

List<WebSourceParams> sourceParams =
        Arrays.asList(sourceParam1, sourceParam2, sourceParam3);
Uri publisherOrigin = Uri.parse("https://publisher.example");
Uri appDestination = Uri.parse("android-app://com.example.store");
Uri webDestination = Uri.parse("https://example.com");

CompletableFuture<Void> future = new CompletableFuture<>();

adView.setOnTouchListener(v, event) -> {
    exampleClickEvent = event;
    return true;
}

WebSourceRegistrationRequest clickRegistrationRequest =
        new WebSourceRegistrationRequest.Builder(sourceParams, publisherOrigin)
    .setAppDestination(appDestination)
    .setWebDestination(webDestination)
    .setInputEvent(event)
    .build();
WebSourceRegistrationRequest viewRegistrationRequest =
        new WebSourceRegistrationRequest.Builder(sourceParams, publisherOrigin)
    .setAppDestination(appDestination)
    .setWebDestination(webDestination)
    .setInputEvent(null)
    .build();

// Register a web source for a click event.
measurementManager.registerWebSource(clickRegistrationRequest,
        CALLBACK_EXECUTOR, future::complete);

// Register a web source for a view event.
measurementManager.registerWebSource(viewRegistrationRequest,
        CALLBACK_EXECUTOR, future::complete);

קטע הקוד הבא מציג דוגמה לקריאה ל-API שהדפדפן מבצע כדי לרשום המרה אחרי שהמשתמש מופנה מהאפליקציה:

Kotlin

companion object {
    private val CALLBACK_EXECUTOR = Executors.newCachedThreadPool()
}

val measurementManager = context.getSystemService(MeasurementManager::class.java)

// Use the URIs of the server-side endpoints that accept trigger registration.
val triggerParam1 = WebTriggerParams.Builder(Uri.parse(
        "https://adtech1.example/trigger?AD_TECH_PROVIDED_METADATA"))
    // True, if debugging is allowed for the ad tech.
    .setDebugKeyAllowed(true)
    .build()

val triggerParam2 = WebTriggerParams.Builder(Uri.parse(
        "https://adtech2.example/trigger?AD_TECH_PROVIDED_METADATA"))
    .setDebugKeyAllowed(false)
    .build()

val triggerParams = Arrays.asList(triggerParam1, triggerParam2)
val advertiserOrigin = Uri.parse("https://advertiser.example")

val future = CompletableFuture<Void>()

val triggerRegistrationRequest = WebTriggerRegistrationRequest.Builder(
        triggerParams,
        advertiserOrigin)
    .build()

// Register the web trigger (conversion).
measurementManager.registerWebTrigger(
    triggerRegistrationRequest,
    CALLBACK_EXECUTOR,
    future::complete)

Java

private static final Executor CALLBACK_EXECUTOR =
        Executors.newCachedThreadPool();

MeasurementManager measurementManager =
        context.getSystemService(MeasurementManager.class);

// Use the URIs of the server-side endpoints that accept trigger registration.
WebTriggerParams triggerParam1 = WebTriggerParams.Builder(Uri.parse(
        "https://adtech1.example/trigger?AD_TECH_PROVIDED_METADATA"))
    // True, if debugging is allowed for the ad tech.
    .setDebugKeyAllowed(true)
    .build();

WebTriggerParams triggerParam2 = WebTriggerParams.Builder(Uri.parse(
        "https://adtech2.example/trigger?AD_TECH_PROVIDED_METADATA"))
    .setDebugKeyAllowed(false)
    .build();

List<WebTriggerParams> triggerParams =
        Arrays.asList(triggerParam1, triggerParam2);
Uri advertiserOrigin = Uri.parse("https://advertiser.example");

CompletableFuture<Void> future = new CompletableFuture<>();

WebTriggerRegistrationRequest triggerRegistrationRequest =
        new WebTriggerRegistrationRequest.Builder(
            triggerParams, advertiserOrigin)
    .build();

// Register the web trigger (conversion).
measurementManager.registerWebTrigger( triggerRegistrationRequest,
        CALLBACK_EXECUTOR, future::complete);

הוספת רעש כדי להגן על הפרטיות

דוחות ברמת האירוע כוללים יעד, מזהה של מקור השיוך ונתוני טריגר. הם נשלחים בפורמט המקורי (לא מוצפן) למקור של הדיווח. כדי להגן על פרטיות המשתמשים, ניתן להוסיף רעש כדי להקשות על הזיהוי של משתמש ספציפי. דוחות ברמת האירוע מופקים ונשלחים בהתאם למסגרת של פרטיות דיפרנציאלית. בטבלה הבאה מפורטים ערכי ברירת המחדל של אחוזי ה"רעש" בתרחישים שונים:

סוג מקור

ערך היעד של המקור

הסתברות לדיווח רועש לכל רישום מקור

מעבר למצב תצוגה

באפליקציה או באתר

0.0000025

מעבר למצב תצוגה

אפליקציה ואתר

0.0000042

קליק

באפליקציה או באתר

0.0024263

קליק

אפליקציה ואתר

0.0170218

במדידה של שיוך מאפליקציה לאתר, שבה מקורות יכולים להגדיל את מספר ההמרות ליעדים של אפליקציות ואתרים, דוחות ברמת האירוע יכולים לציין אם הטריגר התרחש באפליקציה או באתר. כדי לפצות על הפרטים הנוספים האלה, מספר הקליקים המופקים בדוחות גבוה פי 7 בערך ו-1.7 בערך לצפיות.

טכנולוגיות מסוימות של מודעות לא מחייבות דוחות ברמת האירוע כדי לציין אם הטריגר התרחש באפליקציה או ביעד האינטרנט. טכנולוגיות פרסום יכולות להשתמש בשדה coarse_event_report_destinations מתחת לכותרת Attribution-Reporting-Register-Source כדי להפחית את הרעש. אם מקור עם השדה coarse_event_report_destinations שצוין זוכה בשיוך, הדוח שיתקבל יכלול גם את יעדי האפליקציה וגם את יעדי האתר ללא הבחנה לפי המקום שבו התרחש הטריגר בפועל.

בדוגמאות הבאות, משתמש לוחץ על מודעה, והמקור הזה רשום ב-API. לאחר מכן המשתמש משלים המרה גם באפליקציית המפרסם וגם באתר של המפרסם. שתי ההמרות האלו נרשמות כטריגרים ומשוייכות לקליק הראשוני.

כותרת HTTP לרישום מקור המבוססת על קליק:

Attribution-Reporting-Register-Source: {
    "destination": "android-app://com.advertiser.example",
    "web_destination": "https://advertiser.com",
    "source_event_id": "234",
    "expiry": "60000",
    "priority": "5",
    // Ad tech opts out of receiving app-web destination distinction
    // in event report, avoids additional noise
    "coarse_event_report_destinations": "true"
}

נרשם טריגר מהאפליקציה עם שם החבילה com.advertiser.example:

Attribution-Reporting-Register-Trigger: {
    "event_trigger_data": [{
    "trigger_data": "1",
    "priority": "1"
    }],
}

טריגר נרשם מדפדפן מהאתר עם הדומיין eTLD+1 https://advertiser.com:

Attribution-Reporting-Register-Trigger: {
    "event_trigger_data": [{
    "trigger_data": "2",
    "priority": "2"
    }],
}

המערכת יוצרת דוחות ברמת האירוע. בהנחה ששני הטריגרים משויכים למקור, מופקים הדוחות הבאים ברמת האירוע:

  {
    "attribution_destination": ["android-app://com.advertiser.example,https://advertiser.com"],
    "scheduled_report_time": "800176400",
    "source_event_id": "53234",
    "trigger_data": "1",
    // Can be "event" if source were registered by user viewing the ad
    "source_type": "navigation",
    // Would be 0.0170218 without coarse_event_report_destinations as true in the source
    "randomized_trigger_rate": 0.0024263
  }

יצירה ושליחה של דוחות

Attribution Reporting API שולח דוחות לנקודות הקצה בשרת שלכם: דוחות ברמת האירוע ודוחות נצברים.

אילוץ הרצה של משימות דיווח

אחרי שרושמים אירוע של מקור שיוך או רושמים אירוע של טריגר, המערכת מתזמנת את ההרצה של משימת הדיווח. כברירת מחדל, המשימה הזו פועלת בכל 4 שעות. למטרות בדיקה, אפשר לאלץ את משימות הדיווח להריץ את משימות הדיווח או לקצר את המרווחים בין המשימות.

אילוץ ההפעלה של משימת השיוך (Attribution):

adb shell cmd jobscheduler run -f com.google.android.adservices.api 5

אילוץ הרצה של משימת דיווח ברמת האירוע:

adb shell cmd jobscheduler run -f com.google.android.adservices.api 3

אילוץ הרצה של משימת דיווח מצטברת:

adb shell cmd jobscheduler run -f com.google.android.adservices.api 7

כדי לראות מתי המשימות פעלו, יש לבדוק את הפלט ב-Logcat. הוא אמור להיראות כך:

JobScheduler: executeRunCommand(): com.google.android.adservices.api/0 5 s=false f=true

אילוץ מסירה של דוחות

גם אם משימת הדיווח מאולצת להרצה, המערכת עדיין שולחת דוחות בהתאם לזמני האספקה המתוזמנים שלהם, שנעים בין כמה שעות לכמה ימים. למטרות בדיקה, אתם יכולים לקדם את זמן המערכת של המכשיר כך שיהיה מאוחר יותר מהעיכוב שנקבע, כדי להתחיל במסירת הדוח.

אימות דוחות בשרת שלך

אחרי שהדוחות נשלחים, מאמתים את המסירה על ידי בדיקת הדוחות שהתקבלו, יומני השרת הרלוונטיים כמו היסטוריית השרת המדומה או המערכת בהתאמה אישית.

פענוח של דוח הצבירה

כשמתקבלים דוח מצטבר, השדה debug_cleartext_payload מכיל גרסה לא מוצפנת של הדוח הנצבר. הגרסה הזו של הדוח לא מוצפנת, אבל עדיין צריך לפענח אותה.

בהמשך מוצגת דוגמה לפענוח התוכן של השדה debug_cleartext_payload בשני שלבים: הראשון באמצעות פענוח Base 64 והשני באמצעות פענוח באמצעות CBOR.

String base64DebugPayload  = "omRkYXRhgqJldmFsdWVEAAAGgGZidWNrZXRQAAAAAAAAAAAAAAAAAAAKhaJldmFsdWVEAACAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAFWWlvcGVyYXRpb25paGlzdG9ncmFt";
byte[] cborEncoded = Base64.getDecoder().decode(base64DebugPayload);

// CbodDecoder comes from this library https://github.com/c-rack/cbor-java
final List<DataItem> dataItems = new CborDecoder(new ByteArrayInputStream(cborEncoded)).decode();

// In here you can see the contents, but the value will be something like:
// Data items: [{ data: [{ value: co.nstant.in.cbor.model.ByteString@a8b5c07a,
//   bucket: co.nstant.in.cbor.model.ByteString@f812097d },
//   { value: co.nstant.in.cbor.model.ByteString@a8b5dfc0,
//   bucket: co.nstant.in.cbor.model.ByteString@f8120934 }], operation: histogram }]
Log.d("Data items : " + dataItems);

// In order to see the value for bucket and value, you can traverse the data
// and get their values, something like this:
final Map payload = (Map) dataItems.get(0);
final Array payloadArray = (Array) payload.get(new UnicodeString("data"));

payloadArray.getDataItems().forEach(i -> {
    BigInteger value = new BigInteger(((ByteString) ((Map)i).get(new UnicodeString("value"))).getBytes());
    BigInteger bucket = new BigInteger(((ByteString) ((Map)i).get(new UnicodeString("bucket"))).getBytes());
    Log.d("value : " + value + " ;bucket : " + bucket);
});

בדיקה

כדי להתחיל לעבוד עם Attribution Reporting API, תוכלו להשתמש בפרויקט MeasurementSampleApp ב-GitHub. האפליקציה לדוגמה הזו מדגימה את מקור השיוך ומפעילה את הרישום.

לגבי נקודות קצה של שרתים, תוכלו להיעזר במקורות המידע הבאים או בפתרון המותאם אישית שלכם:

  • החלק MeasurementAdTechServerSpec כולל הגדרות של שירות OpenAPI, שאפשר לפרוס בפלטפורמות מדומות או מיקרו-שירותים נתמכות.
  • MeasurementAdTechServer כולל הטמעת הפניה של שרת מדומה על סמך אפליקציית Spring Boot ל-Google App Engine.

דרישות מוקדמות

פריסת ממשקי API מדומים בנקודות קצה מרוחקות שאפשר לגשת אליהן ממכשיר הבדיקה או מהאמולטור. כדי לבצע את הבדיקה בקלות, תוכלו להיעזר בפרויקטים לדוגמה MeasurementAdTechServerSpec ו-MeasurementAdTechServer.

הפונקציונליות לבדיקה

תכונות שיושקו בקרוב

הגדרה גמישה ברמת האירוע

הגדרת ברירת המחדל לדיווח ברמת האירוע מומלצת להתחלת בדיקת כלי תחזוקה, אבל יכול להיות שלא תהיה אידיאלית לכל התרחישים לדוגמה. ה-Attribution Reporting API יתמוך בהגדרות אופציונליות וגמישות יותר, כדי שלטכנולוגיות הפרסום תהיה שליטה רבה יותר על מבנה הדוחות ברמת האירוע ויכולת להפיק את המרב מהנתונים. הגמישות הנוספת הזו תיושם ב-Attribution Reporting API בשני שלבים:

  • שלב 1: הגדרה פשוטה של רמת האירוע הגמישה, קבוצת משנה של שלב 2.
  • שלב 2: גרסה מלאה של הגדרה גמישה ברמת האירוע.

שלב 1: רמת אירוע גמיש בגרסת Lite

נוסיף את שני הפרמטרים האופציונליים הבאים ל-JSON ב-Attribution-Reporting-Register-Source:

  • max_event_level_reports
  • event_report_windows
{
  ...
  // Optional. This is a parameter that acts across all trigger types for the
  // lifetime of this source. It restricts the total number of event-level
  // reports that this source can generate. After this maximum is hit, the
  // source is no longer capable of producing any new data. The use of
  // priority in the trigger attribution algorithm in the case of multiple
  // attributable triggers remains unchanged. Defaults to 3 for navigation
  // sources and 1 for event sources
  "max_event_level_reports": <int>,

  // Optional. Represents a series of time windows, starting at 0. Reports
  // for this source will be delivered an hour after the end of each window.
  // Time is encoded as seconds after source registration. If
  // event_report_windows is omitted, will use the default windows. This
  // field is mutually exclusive with the existing `event_report_window` field.
  // // End time is exclusive.
  "event_report_windows": {
    "start_time": <int>,
    "end_times": [<int>, ...]
  }
}

דוגמה להגדרות מותאמות אישית

התצורה לדוגמה הזו תומכת במפתח שרוצה לבצע אופטימיזציה לקבלת דוחות בחלונות דיווח מוקדמים יותר.

{
  ...
  "max_event_level_reports": 2,
  "event_report_windows": {
    "end_times": [7200, 43200, 86400] // 2 hours, 12 hours, 1 day in seconds
  }
}

שלב 2: רמת אירוע גמיש מלאה

בנוסף לפרמטרים שנוספו בשלב 1, נוסיף עוד פרמטר אופציונלי trigger_specs ל-JSON ב-Attribution-Reporting-Register-Source.

{
  // A trigger spec is a set of matching criteria, along with a scheme to
  // generate bucketized output based on accumulated values across multiple
  // triggers within the specified event_report_window. There will be a limit on
  // the number of specs possible to define for a source.
  "trigger_specs": [{
    // This spec will only apply to registrations that set one of the given
    // trigger data values (non-negative integers) in the list.
    // trigger_data will still appear in the event-level report.
    "trigger_data": [<int>, ...]

    // Represents a series of time windows, starting at the source registration
    // time. Reports for this spec will be delivered an hour after the end of
    // each window. Time is encoded as seconds after source registration.
    // end_times must consist of strictly increasing positive integers.
    //
    // Note: specs with identical trigger_data cannot have overlapping windows;
    // this ensures that triggers match at most one spec. If
    // event_report_windows is omitted, will use the "event_report_window" or
    // "event_report_windows" field specified at the global level for the source
    // (or the default windows if none are specified). End time is exclusive.
    "event_report_windows": {
      "start_time": <int>,
      "end_times": [<int>, ...],
    }

    // Represents an operator that summarizes the triggers within a window
    // count: number of triggers attributed within a window
    // value_sum: sum of the value of triggers within a window
    // The summary is reported as an index into a bucketization scheme. Defaults
    // to "count"
    "summary_window_operator": <one of "count" or "value_sum">,

    // Represents a bucketization of the integers from [0, MAX_INT], encoded as
    // a list of integers where new buckets begin (excluding 0 which is
    // implicitly included).
    // It must consist of strictly increasing positive integers.
    //
    // e.g. [5, 10, 100] encodes the following ranges:
    // [[0, 4], [5, 9], [10, 99], [100, MAX_INT]]
    //
    // At the end of each reporting window, triggers will be summarized into an
    // integer which slots into one of these ranges. Reports will be sent for
    // every new range boundary that is crossed. Reports will never be sent for
    // the range that includes 0, as every source is initialized in this range.
    //
    // If omitted, then represents a trivial mapping
    // [1, 2, ... , MAX_INT]
    // With MAX_INT being the maximum int value defined by the browser.
    "summary_buckets": [<bucket start>, ...]
  }, {
    // Next trigger_spec
  } ...],

  // See description in phase 1.
  "max_event_level_reports": <int>
  // See description in phase 1.
  "event_report_windows": {
    "start_time": <int>,
    "end_times": [<int>, ...]
  }
}

ההגדרה הזו מציינת באופן מלא את מרחב הפלט של הדוחות ברמת האירוע, בכל רישום של המקור. לכל מפרט טריגר, אנחנו מציינים את הפרטים הבאים:

  • קבוצת קריטריונים להתאמה:
    • נתוני הטריגר הספציפיים שעליהם חל המפרט הזה. אפשר להתאים את המקור הזה רק לטריגרים שיש להם אחד מערכי trigger_data שצוינו ב-trigger_specs. במילים אחרות, אם הגורם המפעיל היה תואם למקור הזה, אבל ה-trigger_data שלו הוא לא אחד מהערכים בתצורת המקור, המערכת תתעלם מהטריגר.
    • כשטריגר ספציפי תואם למפרט הזה (באמצעות event_report_windows). חשוב לזכור שעדיין אפשר להתאים את הטריגר למקור של דוחות נצברים, גם אם שני הקריטריונים להתאמה שצוינו קודם נכשלו.
  • אלגוריתם ספציפי לסיכום וסיווג של כל הטריגרים בתוך חלון שיוך (Attribution). כך הטריגרים יכולים לציין פרמטר value שמסכם עבור מפרט מסוים, אבל מדווח כערך המסווג בקטגוריה.

הטריגרים יתמכו גם בהוספת פרמטר ערך אופציונלי במילונים בתוך event_trigger_data.

{
  "event_trigger_data": [
    {
      "trigger_data": "2",
      "value": 100,  // Defaults to 1
      "filters": ...
    },
    ...
  ]
}

כל רישום של טריגר יתאים למפרט טריגר אחד לכל היותר, ויעדכן את ערך הסיכום המשויך אליו. ברמה גבוהה, בזמן ההפעלה:

  • הפעלת מסנני שיוך גלובליים.
  • לכל מפרט טריגר, בודקים את event_trigger_data במפרט כדי למצוא התאמה באמצעות event_reporting_window של המפרט. הרמה העליונה event_reporting_windows משמשת כערך ברירת מחדל במקרה שמפרט טריגר כלשהו הוא שדה המשנה החסר event_report_windows.
  • המפרט הראשון התואם נבחר לשיוך, וערך הסיכום גדל ב-value.

עם השלמת השדה event_report_window למפרט, נמפה את ערך הסיכום שלו לקטגוריה מסוימת ונשלח דוח ברמת האירוע לגבי כל צבירה בקטגוריית הסיכום שנגרמה על ידי ערכי טריגר משויכים. בדוחות יופיע שדה נוסף אחד, trigger_summary_bucket.

{
  ...
  "trigger_summary_bucket": [<bucket start>, <bucket end>],
}

הגדרות מקבילות לגרסה הנוכחית

ההגדרות הבאות מקבילות לממשקי ה-API של האירוע הנוכחי ולמקורות הניווט, בהתאמה. הקטע הזה ממחיש במיוחד למה רמות הרעש גבוהות כל כך ביחס למקורות של אירועים כדי לשמור על אותם ערכי אפסילון: למקורות הניווט יש מרחב פלט הרבה יותר גדול.

יכול להיות שיש כמה הגדרות מקבילות, כי אפשר להגדיר חלק מהפרמטרים כברירת מחדל או לחתוך את הפרמטרים.

מקורות אירועים מקבילים
// Note: most of the fields here are not required to be explicitly listed.
// Here we list them explicitly just for clarity.
{
  "trigger_specs": [
  {
    "trigger_data": [0, 1],
    "event_report_windows": {
      "end_times": [<30 days>]
    },
    "summary_window_operator": "count",
    "summary_buckets": [1],
  }],
  "max_event_level_reports": 1,
  ...
  // expiry must be greater than or equal to the last element of the end_times
  "expiry": <30 days>,
}
מקורות ניווט מקבילים
// Note: most of the fields here are not required to be explicitly listed.
// Here we list them explicitly just for clarity.
{
  "trigger_specs": [
  {
    "trigger_data": [0, 1, 2, 3, 4, 5, 6, 7],
    "event_report_windows": {
      "end_times": [<2 days>, <7 days>, <30 days>]
    },
    "summary_window_operator": "count",
    "summary_buckets": [1, 2, 3],
  }],
  "max_event_level_reports": 3,
  ...
  // expiry must be greater than or equal to the last element of the end_times
  "expiry": <30 days>,
}

הגדרות אישיות לדוגמה

בהמשך מפורטות כמה הגדרות נוספות מחוץ לברירות המחדל. בכל הדוגמאות האלה, הפשרה של המפתחים כוללת:

  • הפחתת מימד מסוים של הגדרות ברירת המחדל (#triggers, עוצמה (cardinality) של נתונים, #windows) כדי להגדיל עוד מאפיין, כדי לשמור על רמת הרעש
  • הפחתת מימד מסוים של הגדרות ברירת המחדל (#triggers, עוצמה (cardinality) של נתונים, הפעלת #windows) לרמת רעש מופחתת

קטגוריות ערך של טריגר לדיווח

בדוגמה הזו יש תמיכה במפתח שרוצה לבצע אופטימיזציה לנתוני ערך בחלון דיווח אחד בלבד (למשל, 7 ימים), ולסחור בפחות חלונות דיווח כדי להפחית את הרעש. בדוגמה הזו, כל טריגר שמגדיר את trigger_data לערך שאינו 0 לא עומד בדרישות לשיוך.

{
  "trigger_specs": [
  {
    "trigger_data": [0],
    "event_report_windows": {
      "end_times": [604800, 1209600] // 7 days, 14 days represented in seconds
    },
    "summary_window_operator": "value_sum",
    "summary_buckets": [5, 10, 100]
  }],
}

אפשר לרשום את הטריגרים בקבוצת השדות value, שסוכמו ומסווגות לפי קטגוריות. לדוגמה, אם יש שלושה טריגרים תוך 7 ימים מרגע רישום המקור עם הערכים 1, 3 ו-4.

{ "event_trigger_data": [{"trigger_data": "0", "value": 1}] }
{ "event_trigger_data": [{"trigger_data": "0", "value": 3}] }
{ "event_trigger_data": [{"trigger_data": "0", "value": 4}] }

הערכים מסוכמים ל-8 ומדווחים בדוחות הבאים אחרי 7 ימים + שעה:

// Report 1
{
  ...
  "trigger_summary_bucket": [5, 9]
}

במהלך 7 הימים הבאים, המערכת מתעדת את הטריגרים הבאים:

{ "event_trigger_data": [{"trigger_data": "0", "value": 50}] }
{ "event_trigger_data": [{"trigger_data": "0", "value": 45}] }

מסוכמים את הערכים ב-8 + 50 + 45 = 103. אלה הדוחות שיניבו ב-14 ימים + שעה:

// Report 2
{
  ...
  "trigger_summary_bucket": [10, 99]
},

// Report 3
{
  ...
  "trigger_summary_bucket": [100, MAX_INT]
}
ספירת טריגרים בדוחות

הדוגמה הזו ממחישה איך מפתח יכול להגדיר מקור כדי לקבל ספירה של עד 10.

{
  "trigger_specs": [
  {
    "trigger_data": [0],
    "event_report_windows": {
      "end_times": [604800] // 7 days represented in seconds
    },
    // This field could be omitted to save bandwidth since the default is "count"
    "summary_window_operator": "count",
    "summary_buckets": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  }],
}

טריגרים משויכים עם ערך של trigger_data לערך 0 נספרים ומוגבלים ל-10. המערכת מתעלמת מערך הטריגר כי summary_window_operator מוגדר לספירה. אם רשומים 4 טריגרים ומיוחסים למקור, הדוח ייראה כך:

// Report 1
{
  ...
  "trigger_summary_bucket": [1, 1]
}
// Report 2
{
  ...
  "trigger_summary_bucket": [2, 2]
}
// Report 3
{
  ...
  "trigger_summary_bucket": [3, 3]
}
// Report 4
{
  ...
  "trigger_summary_bucket": [4, 4]
}
בינאריות עם דיווח תדיר יותר

בדוגמה הזו יש תמיכה למפתח שרוצה לדעת אם לפחות המרה אחת התרחשה ב-10 הימים הראשונים (ללא קשר לערך), אבל רוצה לקבל דוחות במרווחי זמן תכופים יותר מאשר ברירת המחדל. שוב, בדוגמה הזו, כל טריגר שמגדיר את trigger_data לערך שאינו 0 לא מתאים לשיוך. לכן התרחיש הזה נקרא בינארי.

{
  "trigger_specs": [
  {
    "trigger_data": [0],
    "event_report_windows": {
      // 1 day, 2 days, 3 days, 5 days, 7 days, 10 days represented in seconds
      "end_times": [86400, 172800, 259200, 432000, 604800, 864000]
    },
    // This field could be omitted to save bandwidth since the default is "count"
    "summary_window_operator": "count",
    "summary_buckets": [1]
  }],
}
שינוי מפרטי הטריגר מהמקור למקור
{
  "trigger_specs": [
  {
    "trigger_data": [0, 1, 2, 3],
    "event_report_windows": {
      "end_times": [172800, 604800, 2592000] // 2 days, 7 days, 30 days represented in seconds
    }
  }],
  "max_event_level_reports": 3
}
{
  "trigger_specs": [
  {
    "trigger_data": [4, 5, 6, 7],
    "event_report_windows": {
      "end_times": [172800, 604800, 2592000] // 2 days, 7 days, 30 days represented in seconds
    }
  }],
  "max_event_level_reports": 3
}

אנחנו ממליצים למפתחים להציע תרחישים שונים לדוגמה לתוסף ה-API הזה, ואנחנו נעדכן את ההסבר בעזרת הגדרות לדוגמה של תרחישי השימוש האלה.

שיוך (Attribution) חוצה-רשתות ללא הפניות לכתובות אחרות

טכנולוגיות פרסום צריכות להשתמש בהפניות לכתובות אחרות כדי לרשום מספר טריגרים של מקור השיוך, וכדי לבצע שיוך חוצה-פלטפורמות. התכונה הזו עוזרת לתמוך בשיוך חוצה-פלטפורמות, במקרים שבהם לא ניתן להשתמש בהפניות אוטומטיות ברשתות שונות. מידע נוסף

טכנולוגיות פרסום יכולות לשלוח הגדרות בתגובה לרישום של הטריגר, על סמך המקורות שנרשמו על ידי טכנולוגיות פרסום אחרות שנבחרו ליצירת מקורות נגזרים. המקורות הנגזרים האלה משמשים לשיוך (Attribution). דוחות מצטברים נוצרים אם הטריגר משויך למקור נגזר. אין תמיכה ביצירת דוח אירועים למקורות נגזרים.

טכנולוגיות פרסום יכולות לבחור מבין aggregation_keys במקורות הרשומים שלהן שבהן היא מתכוונת לשתף עם טכנולוגיות פרסום של שותפים. ניתן להצהיר על המפתחות האלה בשדה shared_aggregation_keys האופציונלי, שנמצא מתחת לכותרת Attribution-Reporting-Register-Source של רישום המקור:

"shared_aggregation_keys": ["[key name1]", "[key name2]"]

המקורות הנגזרים נוצרים על סמך ההגדרה של כותרת הרישום Attribution-Reporting-Register-Trigger של הטריגר:

  // Specifies the configuration based on which derived sources should be
  // generated. Those derived sources will be included for source matching at the
  // time of attribution. For example, if adtech2 is registering a trigger with an
  // attribution_config with source_network as adtech1, available sources
  // registered by adtech1 will be considered with additional filtering criteria
  // applied to that set as mentioned in the attribution_config. Derived
  // sources can have different values to priority, post_install_exclusivity_window
  // etc.

  "attribution_config": [
    {
      // Derived sources are created from this adtech's registered sources
      "source_network": "[original source's adtech enrollment ID]",
      //(optional) Filter sources whose priority falls in this range
      "source_priority_range": {
        "start": [priority filter lower bound],
        "end": [priority filter upper bound]
      },
      // (optional) Filter sources whose at least one of filter maps matches these
      // filters
      "source_filters": {
        "key name 1": ["key1 value 1"]
      },
      // (optional) Filter sources whose none of filter map matches these
      // filters
        "source_not_filters": {
          "key name 1": ["key1 value 1"]
        },
      // (optional) Apply this priority to the generated derived sources
      "priority": "[64 bit signed integer]",
      // (optional) The derived source will have expiry set as this or parent
      // source's, whichever is earlier
      "expiry": "[64 bit signed integer]",
      // (optional) set on the derived source
      "filter_data": {
        "key name 1": ["key1 value 1"]
      },
      // (optional) set on the derived source
      "post_install_exclusivity_window": "[64-bit unsigned integer]"
    }
  ]

הנה גרסה שנוספו לה ערכים לדוגמה:

  "attribution_config": [
    {
      "source_network": "adtech1-enrollment-id",
      "source_priority_range": {
        "start": 50,
        "end": 100
      },
      "source_filters": {
        "source_type": ["NAVIGATION"]
      },
      "source_not_filters": {
        "product_id": ["789"]
      },
      "priority": "30",
      "expiry": "78901",
      // (optional) set on the derived source
      "filter_data": {
        "product_id": ["1234"]
        },
      // (optional) set on the derived source
      "post_install_exclusivity_window": "7890"
    }
  ]

נוספו שני שדות אופציונליים חדשים כדי להפעיל כותרת רישום. השדות הבאים מפעילים את המזהה של טכנולוגיית הפרסום הזוכה במפתחות נתונים נצברים:

  • x_network_bit_mapping: מזהה ההרשמה למיפוי הסיביות של מזהה הפרסום הדיגיטלי
  • x_network_data: היסט (היסט שמאל) של טכנולוגיית המודעות הזוכה x_network_bit_mapping או פעולה באמצעות מקש הטריגר
דוגמה:
"Attribution-Reporting-Register-Trigger": {
  "attribution_config": [...],
  "aggregatable_trigger_data": [
    {
     "key_piece": "0x400",
     "source_keys": ["campaignCounts"]
      "x_network_data" : {
        "key_offset" : 12 // [64 bit unsigned integer]
      }
    }
    …
  ]
  …
  "x_network_bit_mapping": {
   // This mapping is used to generate trigger key pieces with AdTech identifier
   // bits. eg. If AdTechA's sources wins the attribution then 0x1 here will be
   // OR'd with the trigger key pieces to generate the final key piece.
    "AdTechA-enrollment_id": "0x1", // Identifier bits in hex for A
    "AdTechB-enrollment_id": "0x2"  // Identifier bits in hex for B
  }
  …
}

לפניכם החישוב של קטע המפתח של הטריגר שנוצר, כשנוצר דוח עבור המקור של AdTechB:

  • key_piece:‏ 0x400 (010000000000)
  • key_offset:‏ 12
  • הערך enrollment_id של AdtechB: 2 (010) (מ-x_network_bit_mapping)
  • קטע מפתח טריגר שהתקבל כתוצאה מכך: 0x400 | 0x2 << 12 = 0x2400

מגבלות

בנתוני הגרסה תוכלו למצוא את רשימת היכולות שמתבצעת בזמן הריצה של ה-SDK.

דיווח על באגים ובעיות

המשוב שלך הוא חלק חיוני מארגז החול לפרטיות ב-Android! תוכלו לספר לנו על בעיות שמצאתם או על רעיונות לשיפור ארגז החול לפרטיות ב-Android.