Руководство разработчика API отчетов об атрибуции,Руководство разработчика API отчетов об атрибуции,Руководство разработчика API отчетов об атрибуции

для разработчиков

Читая документацию Privacy Sandbox для Android, используйте кнопку Developer Preview или Beta , чтобы выбрать версию программы, с которой вы работаете, поскольку инструкции могут отличаться.


API отчетов по атрибуции предназначен для обеспечения улучшенной конфиденциальности пользователей за счет устранения зависимости от межпартийных идентификаторов пользователей, а также для поддержки ключевых вариантов использования для атрибуции и измерения конверсий в приложениях. В этом руководстве разработчика описывается, как настроить и протестировать API отчетов по атрибуции для регистрации кликов, просмотров и конверсий объявлений путем вызова методов, которые регистрируют соответствующие триггеры и источники для таких событий.

В этом руководстве вы узнаете, как настроить конечные точки сервера и создать клиентское приложение, вызывающее эти службы. Подробную информацию об общей конструкции API отчетов об атрибуции можно найти в предложении по дизайну .

Ключевые термины

  • Источниками атрибуции являются клики или просмотры.
  • Триггеры — это события, которые можно отнести к конверсиям.
  • Отчеты содержат данные о триггере и соответствующем источнике атрибуции. Эти отчеты отправляются в ответ на триггерные события. API отчетов по атрибуции поддерживает отчеты на уровне событий и агрегированные отчеты .

Прежде чем начать

Чтобы использовать API отчетов по атрибуции, выполните задачи на стороне сервера и на стороне клиента, перечисленные в следующих разделах.

Настройка конечных точек API отчетов по атрибуции

Для API отчетов по атрибуции требуется набор конечных точек, к которым вы можете получить доступ с тестового устройства или эмулятора. Создайте одну конечную точку для каждой из следующих задач на стороне сервера:

Существует несколько способов настройки необходимых конечных точек:

  • Самый быстрый способ приступить к работе — развернуть определения служб OpenAPI v3 из нашего репозитория примеров кода на макетной платформе или платформе микросервисов. Вы можете использовать Postman , Prism или любую другую платформу макетного сервера, которая принимает этот формат. Разверните каждую конечную точку и отслеживайте URI для использования в вашем приложении. Чтобы проверить доставку отчета, обратитесь к ранее выполненным вызовам к макетной или бессерверной платформе.
  • Запустите собственный автономный сервер, используя образец Kotlin на основе Spring Boot . Разверните этот сервер у своего облачного провайдера или во внутренней инфраструктуре.
  • Используйте определения служб в качестве примеров для интеграции конечных точек в существующую систему.

Принять регистрацию источника

Эта конечная точка должна быть адресована через URI, подобный следующему:

https://adtech.example/attribution_source

Когда клиентское приложение регистрирует источник атрибуции , оно предоставляет URI для этой конечной точки сервера. Затем 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 партнеров по рекламным технологиям, API 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 для этой конечной точки сервера. Затем 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 партнеров по рекламным технологиям, API 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 см. в разделе Регистрация учетной записи Privacy Sandbox . (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]",
}

Ключи отладки позволяют получить дополнительную информацию об отчетах по атрибуции; Узнайте больше об их настройке.

Принимать агрегированные отчеты

Эта конечная точка должна быть доступна по URI. Дополнительные сведения о регистрации URI см. в разделе Регистрация учетной записи Privacy Sandbox . (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]"
}

Ключи отладки позволяют получить дополнительную информацию об отчетах по атрибуции; Узнайте больше об их настройке.

Настройка Android-клиента

Клиентское приложение регистрирует источники атрибуции и триггеры, а также позволяет создавать агрегированные отчеты на уровне событий и агрегированные отчеты. Чтобы подготовить клиентское устройство или эмулятор Android для использования API отчетов об атрибуции, выполните следующие действия:

  1. Настройте среду разработки для Privacy Sandbox на Android.
  2. Установите образ системы на поддерживаемое устройство или настройте эмулятор , включающий поддержку Privacy Sandbox на Android.
  3. Включите доступ к API отчетов об атрибуции, выполнив следующую команду ADB. (API отключен по умолчанию.)

    adb shell device_config put adservices ppapi_app_allow_list \"\*\"
  4. Если вы локально тестируете API отчетов по атрибуции (например, тестируете на устройстве, к которому у вас есть физический доступ), запустите эту команду, чтобы отключить регистрацию:

    adb shell device_config put adservices disable_measurement_enrollment_check "true"
  5. Включите разрешение ACCESS_ADSERVICES_ATTRIBUTION в файл манифеста Android и создайте конфигурацию рекламных служб для своего приложения, чтобы использовать 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 содержит методы, помогающие регистрировать события источников атрибуции и триггеры конверсий .

Регистрация события источника атрибуции

Когда объявление просматривается или нажимается на него, приложение издателя вызывает registerSource() чтобы зарегистрировать источник атрибуции, как показано во фрагменте кода.

API отчетов по атрибуции поддерживает следующие типы событий источников атрибуции:

  • Клики, которые вы обычно регистрируете с помощью метода обратного вызова, аналогичного onClick() . Соответствующее триггерное событие обычно происходит вскоре после события щелчка. Этот тип событий предоставляет больше информации о взаимодействии с пользователем и, следовательно, является хорошим источником атрибуции, которому можно придать высокий приоритет.
  • Представления, которые вы обычно регистрируете в методе обратного вызова, аналогичном onAdShown() . Соответствующее триггерное событие может произойти через несколько часов или дней после события просмотра.

Котлин

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)

Ява

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 и заголовок «Местоположение» со следующим URL-адресом, который нужно посетить для дополнительной регистрации.

В цепочке используется только поле «назначение», указанное при первом посещении. Количество посещений имеет тот же лимит, что и заголовки «Атрибуция-Отчетность-Перенаправление». Эта поддержка перенаправления дополняет существующую поддержку «Атрибуция-Отчетность-Перенаправление», и если оба они присутствуют, предпочтение отдается «Атрибуции-Отчетности-Перенаправлению».

Регистрация события-триггера конверсии

Чтобы зарегистрировать событие триггера конверсии, вызовите registerTrigger() в своем приложении:

Котлин

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)

Ява

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, который браузер выполняет для регистрации источника атрибуции перед тем, как направить пользователя в приложение:

Котлин

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)

Ява

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, который браузер выполняет для регистрации конверсии после того, как пользователь был направлен из приложения:

Котлин

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)

Ява

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

При измерении атрибуции между приложением и сайтом, где источники могут стимулировать конверсию как в приложение, так и в веб-адресах, в отчетах на уровне событий можно указать, произошел ли триггер в приложении или на сайте. Чтобы компенсировать эту дополнительную детализацию, генерируемые отчеты с шумом составляют до ~7x для кликов и ~1,7x для просмотров.

Некоторым рекламным технологиям не требуются отчеты на уровне событий, чтобы указать, произошел ли триггер в приложении или в веб-адресе. Специалисты по рекламе могут использовать 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
  }

Формировать и доставлять отчеты

API отчетов об атрибуции отправляет отчеты на конечные точки на вашем сервере, которые принимают отчеты на уровне событий и агрегированные отчеты .

Принудительное выполнение заданий отчетов

После регистрации события источника атрибуции или события-триггера система планирует запуск задания создания отчетов. По умолчанию это задание выполняется каждые 4 часа. В целях тестирования вы можете принудительно запускать задания отчетов или сократить интервалы между заданиями.

Принудительное выполнение задания атрибуции:

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);
});

Тестирование

Чтобы помочь вам начать работу с API отчетов по атрибуции, вы можете использовать проект MeasurementSampleApp на GitHub. В этом примере приложения демонстрируется регистрация источника атрибуции и регистрация триггера.

Для конечных точек сервера рассмотрите следующие справочные ресурсы или собственное решение:

  • MeasurementAdTechServerSpec включает определения сервисов OpenAPI, которые можно развернуть на поддерживаемых платформах макетов или микросервисов.
  • MeasurementAdTechServer включает эталонную реализацию макета сервера на основе приложения Spring Boot для Google App Engine.

Предварительные условия

Развертывайте фиктивные API на удаленных конечных точках, доступных с вашего тестового устройства или эмулятора. Для простоты тестирования обратитесь к примерам проектов MeasurementAdTechServerSpec и MeasurementAdTechServer .

Функциональность для тестирования

  • Используйте регистрацию источника атрибуции и триггера конверсии. Убедитесь, что конечные точки на стороне сервера отвечают в правильном формате.
  • Выполнение заданий по отчетности .
  • Проверьте доставку отчетов на серверной части или консоли вашего тестового сервера.

Будущие функции

Гибкая настройка на уровне событий

Конфигурация по умолчанию для отчетов на уровне событий рекомендуется для начала тестирования утилиты, но может не подойти для всех случаев использования. API отчетов по атрибуции будет поддерживать дополнительные, более гибкие конфигурации, чтобы специалисты по рекламе могли лучше контролировать структуру своих отчетов на уровне событий и максимизировать полезность данных. Эта дополнительная гибкость будет реализована в 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 ). Обратите внимание, что триггер по-прежнему может сопоставляться с источником агрегируемых отчетов, несмотря на несоответствие двум критериям соответствия, упомянутым ранее.
  • Специальный алгоритм суммирования и группирования всех триггеров в окне атрибуции. Это позволяет триггерам указывать параметр 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, количество данных триггера, #windows) для увеличения другого, чтобы сохранить уровень шума
  • уменьшение некоторых размеров конфигурации по умолчанию (#triggers, количество данных триггера, #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 дней + 1 час:

// 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 дней + 1 час:

// 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 установлено значение count. Если зарегистрировано 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, и мы будем обновлять это объяснение примерами конфигураций для этих вариантов использования.

Межсетевая атрибуция без редиректов

Специалистам по рекламе следует использовать перенаправления для регистрации нескольких триггеров источников атрибуции и выполнения межсетевой атрибуции. Эта функция помогает поддерживать межсетевую атрибуцию, когда перенаправление между сетями невозможно. Узнать больше .

Рекламные специалисты могут отправлять конфигурацию в ответе на регистрацию триггера на основе того, какие источники, зарегистрированные другими рекламными специалистами, выбраны для создания производных источников; эти производные источники затем используются для атрибуции. Агрегированные отчеты создаются, если триггер приписывается производному источнику. Генерация отчетов о событиях для производных источников не поддерживается.

Рекламные специалисты могут выбирать из 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 см. в примечаниях к выпуску .

Сообщайте об ошибках и проблемах

Ваш отзыв – важная часть Privacy Sandbox на Android! Сообщайте нам о любых обнаруженных проблемах или идеях по улучшению Privacy Sandbox на Android.

,
для разработчиков

Читая документацию Privacy Sandbox для Android, используйте кнопку Developer Preview или Beta , чтобы выбрать версию программы, с которой вы работаете, поскольку инструкции могут отличаться.


API отчетов по атрибуции предназначен для обеспечения улучшенной конфиденциальности пользователей за счет устранения зависимости от межпартийных идентификаторов пользователей, а также для поддержки ключевых вариантов использования для атрибуции и измерения конверсий в приложениях. В этом руководстве разработчика описывается, как настроить и протестировать API отчетов по атрибуции для регистрации кликов, просмотров и конверсий объявлений путем вызова методов, которые регистрируют соответствующие триггеры и источники для таких событий.

В этом руководстве вы узнаете, как настроить конечные точки сервера и создать клиентское приложение, вызывающее эти службы. Подробную информацию об общей конструкции API отчетов об атрибуции можно найти в предложении по дизайну .

Ключевые термины

  • Источниками атрибуции являются клики или просмотры.
  • Триггеры — это события, которые можно отнести к конверсиям.
  • Отчеты содержат данные о триггере и соответствующем источнике атрибуции. Эти отчеты отправляются в ответ на триггерные события. API отчетов по атрибуции поддерживает отчеты на уровне событий и агрегированные отчеты .

Прежде чем начать

Чтобы использовать API отчетов по атрибуции, выполните задачи на стороне сервера и на стороне клиента, перечисленные в следующих разделах.

Настройка конечных точек API отчетов по атрибуции

Для API отчетов по атрибуции требуется набор конечных точек, к которым вы можете получить доступ с тестового устройства или эмулятора. Создайте одну конечную точку для каждой из следующих задач на стороне сервера:

Существует несколько способов настройки необходимых конечных точек:

  • Самый быстрый способ приступить к работе — развернуть определения служб OpenAPI v3 из нашего репозитория примеров кода на макетной платформе или платформе микросервисов. Вы можете использовать Postman , Prism или любую другую платформу макетного сервера, которая принимает этот формат. Разверните каждую конечную точку и отслеживайте URI для использования в вашем приложении. Чтобы проверить доставку отчета, обратитесь к ранее выполненным вызовам к макетной или бессерверной платформе.
  • Запустите собственный автономный сервер, используя образец Kotlin на основе Spring Boot . Разверните этот сервер у своего облачного провайдера или во внутренней инфраструктуре.
  • Используйте определения служб в качестве примеров для интеграции конечных точек в существующую систему.

Принять регистрацию источника

Эта конечная точка должна быть адресована через URI, подобный следующему:

https://adtech.example/attribution_source

Когда клиентское приложение регистрирует источник атрибуции , оно предоставляет URI для этой конечной точки сервера. Затем 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 партнеров по рекламным технологиям, API 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 для этой конечной точки сервера. Затем 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 партнеров по рекламным технологиям, API 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 см. в разделе Регистрация учетной записи Privacy Sandbox . (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]",
}

Ключи отладки позволяют получить дополнительную информацию об отчетах по атрибуции; Узнайте больше об их настройке.

Принимать агрегированные отчеты

Эта конечная точка должна быть доступна по URI. Дополнительные сведения о регистрации URI см. в разделе Регистрация учетной записи Privacy Sandbox . (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]"
}

Ключи отладки позволяют получить дополнительную информацию об отчетах по атрибуции; Узнайте больше об их настройке.

Настройка Android-клиента

Клиентское приложение регистрирует источники атрибуции и триггеры, а также позволяет создавать агрегированные отчеты на уровне событий и агрегированные отчеты. Чтобы подготовить клиентское устройство или эмулятор Android для использования API отчетов об атрибуции, выполните следующие действия:

  1. Настройте среду разработки для Privacy Sandbox на Android.
  2. Установите образ системы на поддерживаемое устройство или настройте эмулятор , включающий поддержку Privacy Sandbox на Android.
  3. Включите доступ к API отчетов об атрибуции, выполнив следующую команду ADB. (API отключен по умолчанию.)

    adb shell device_config put adservices ppapi_app_allow_list \"\*\"
  4. Если вы локально тестируете API отчетов по атрибуции (например, тестируете на устройстве, к которому у вас есть физический доступ), запустите эту команду, чтобы отключить регистрацию:

    adb shell device_config put adservices disable_measurement_enrollment_check "true"
  5. Включите разрешение ACCESS_ADSERVICES_ATTRIBUTION в файл манифеста Android и создайте конфигурацию рекламных служб для своего приложения, чтобы использовать 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 содержит методы, помогающие регистрировать события источников атрибуции и триггеры конверсий .

Регистрация события источника атрибуции

Когда объявление просматривается или нажимается на него, приложение издателя вызывает registerSource() чтобы зарегистрировать источник атрибуции, как показано во фрагменте кода.

API отчетов по атрибуции поддерживает следующие типы событий источников атрибуции:

  • Клики, которые вы обычно регистрируете с помощью метода обратного вызова, аналогичного onClick() . Соответствующее триггерное событие обычно происходит вскоре после события щелчка. Этот тип событий предоставляет больше информации о взаимодействии с пользователем и, следовательно, является хорошим источником атрибуции, которому можно придать высокий приоритет.
  • Представления, которые вы обычно регистрируете в методе обратного вызова, аналогичном onAdShown() . Соответствующее триггерное событие может произойти через несколько часов или дней после события просмотра.

Котлин

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)

Ява

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 и заголовок «Местоположение» со следующим URL-адресом, который нужно посетить для дополнительной регистрации.

В цепочке используется только поле «назначение», указанное при первом посещении. Количество посещений имеет тот же лимит, что и заголовки «Атрибуция-Отчетность-Перенаправление». Эта поддержка перенаправления дополняет существующую поддержку «Атрибуция-Отчетность-Перенаправление», и если оба они присутствуют, предпочтение отдается «Атрибуции-Отчетности-Перенаправлению».

Регистрация события-триггера конверсии

Чтобы зарегистрировать событие триггера конверсии, вызовите registerTrigger() в своем приложении:

Котлин

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)

Ява

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, который браузер выполняет для регистрации источника атрибуции перед тем, как направить пользователя в приложение:

Котлин

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)

Ява

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, который браузер выполняет для регистрации конверсии после того, как пользователь был направлен из приложения:

Котлин

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)

Ява

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

При измерении атрибуции между приложением и сайтом, где источники могут стимулировать конверсию как в приложение, так и в веб-адресах, в отчетах на уровне событий можно указать, произошел ли триггер в приложении или на сайте. Чтобы компенсировать эту дополнительную детализацию, генерируемые отчеты с шумом составляют до ~7x для кликов и ~1,7x для просмотров.

Некоторым рекламным технологиям не требуются отчеты на уровне событий, чтобы указать, произошел ли триггер в приложении или в веб-адресе. Специалисты по рекламе могут использовать 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
  }

Формировать и доставлять отчеты

API отчетов об атрибуции отправляет отчеты на конечные точки на вашем сервере, которые принимают отчеты на уровне событий и агрегированные отчеты .

Принудительное выполнение заданий отчетов

После регистрации события источника атрибуции или события-триггера система планирует запуск задания создания отчетов. По умолчанию это задание выполняется каждые 4 часа. В целях тестирования вы можете принудительно запускать задания отчетов или сократить интервалы между заданиями.

Принудительное выполнение задания атрибуции:

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);
});

Тестирование

Чтобы помочь вам начать работу с API отчетов по атрибуции, вы можете использовать проект MeasurementSampleApp на GitHub. В этом примере приложения демонстрируется регистрация источника атрибуции и регистрация триггера.

Для конечных точек сервера рассмотрите следующие справочные ресурсы или собственное решение:

  • MeasurementAdTechServerSpec включает определения сервисов OpenAPI, которые можно развернуть на поддерживаемых платформах макетов или микросервисов.
  • MeasurementAdTechServer включает эталонную реализацию макета сервера на основе приложения Spring Boot для Google App Engine.

Предварительные условия

Развертывайте фиктивные API на удаленных конечных точках, доступных с вашего тестового устройства или эмулятора. Для простоты тестирования обратитесь к примерам проектов MeasurementAdTechServerSpec и MeasurementAdTechServer .

Функциональность для тестирования

  • Используйте регистрацию источника атрибуции и триггера конверсии. Убедитесь, что конечные точки на стороне сервера отвечают в правильном формате.
  • Выполнение заданий по отчетности .
  • Проверьте доставку отчетов на серверной части или консоли вашего тестового сервера.

Будущие функции

Гибкая настройка на уровне событий

Конфигурация по умолчанию для отчетов на уровне событий рекомендуется для начала тестирования утилиты, но может не подойти для всех случаев использования. API отчетов по атрибуции будет поддерживать дополнительные, более гибкие конфигурации, чтобы специалисты по рекламе могли лучше контролировать структуру своих отчетов на уровне событий и максимизировать полезность данных. Эта дополнительная гибкость будет реализована в 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 ). Обратите внимание, что триггер по-прежнему может сопоставляться с источником агрегируемых отчетов, несмотря на несоответствие двум критериям соответствия, упомянутым ранее.
  • Специальный алгоритм суммирования и группирования всех триггеров в окне атрибуции. Это позволяет триггерам указывать параметр 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, количество данных триггера, #windows) для увеличения другого, чтобы сохранить уровень шума
  • уменьшение некоторых размеров конфигурации по умолчанию (#triggers, количество данных триггера, #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 дней + 1 час:

// 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 дней + 1 час:

// 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 установлено значение count. Если зарегистрировано 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, и мы будем обновлять это объяснение примерами конфигураций для этих вариантов использования.

Межсетевая атрибуция без редиректов

Специалистам по рекламе следует использовать перенаправления для регистрации нескольких триггеров источников атрибуции и выполнения межсетевой атрибуции. Эта функция помогает поддерживать межсетевую атрибуцию, когда перенаправление между сетями невозможно. Узнать больше .

Рекламные специалисты могут отправлять конфигурацию в ответе на регистрацию триггера на основе того, какие источники, зарегистрированные другими рекламными специалистами, выбраны для создания производных источников; эти производные источники затем используются для атрибуции. Агрегированные отчеты создаются, если триггер приписывается производному источнику. Генерация отчетов о событиях для производных источников не поддерживается.

Рекламные специалисты могут выбирать из 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 см. в примечаниях к выпуску .

Сообщайте об ошибках и проблемах

Ваш отзыв – важная часть Privacy Sandbox на Android! Сообщайте нам о любых обнаруженных проблемах или идеях по улучшению Privacy Sandbox на Android.

,
для разработчиков

Читая документацию Privacy Sandbox для Android, используйте кнопку Developer Preview или Beta , чтобы выбрать версию программы, с которой вы работаете, поскольку инструкции могут отличаться.


API отчетов по атрибуции предназначен для обеспечения улучшенной конфиденциальности пользователей за счет устранения зависимости от межпартийных идентификаторов пользователей, а также для поддержки ключевых вариантов использования для атрибуции и измерения конверсий в приложениях. В этом руководстве разработчика описывается, как настроить и протестировать API отчетов по атрибуции для регистрации кликов, просмотров и конверсий объявлений путем вызова методов, которые регистрируют соответствующие триггеры и источники для таких событий.

В этом руководстве вы узнаете, как настроить конечные точки сервера и создать клиентское приложение, вызывающее эти службы. Подробную информацию об общей конструкции API отчетов об атрибуции можно найти в предложении по дизайну .

Ключевые термины

  • Источниками атрибуции являются клики или просмотры.
  • Триггеры — это события, которые можно отнести к конверсиям.
  • Отчеты содержат данные о триггере и соответствующем источнике атрибуции. Эти отчеты отправляются в ответ на триггерные события. API отчетов по атрибуции поддерживает отчеты на уровне событий и агрегированные отчеты .

Прежде чем начать

Чтобы использовать API отчетов по атрибуции, выполните задачи на стороне сервера и на стороне клиента, перечисленные в следующих разделах.

Настройка конечных точек API отчетов по атрибуции

Для API отчетов по атрибуции требуется набор конечных точек, к которым вы можете получить доступ с тестового устройства или эмулятора. Создайте одну конечную точку для каждой из следующих задач на стороне сервера:

Существует несколько способов настройки необходимых конечных точек:

  • Самый быстрый способ приступить к работе — развернуть определения служб OpenAPI v3 из нашего репозитория примеров кода на макетной платформе или платформе микросервисов. Вы можете использовать Postman , Prism или любую другую платформу макетного сервера, которая принимает этот формат. Разверните каждую конечную точку и отслеживайте URI для использования в вашем приложении. Чтобы проверить доставку отчета, обратитесь к ранее выполненным вызовам к макетной или бессерверной платформе.
  • Запустите собственный автономный сервер, используя образец Kotlin на основе Spring Boot . Разверните этот сервер у своего облачного провайдера или во внутренней инфраструктуре.
  • Используйте определения служб в качестве примеров для интеграции конечных точек в существующую систему.

Принять регистрацию источника

Эта конечная точка должна быть адресована через URI, подобный следующему:

https://adtech.example/attribution_source

Когда клиентское приложение регистрирует источник атрибуции , оно предоставляет URI для этой конечной точки сервера. Затем 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 партнеров по рекламным технологиям, API 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 для этой конечной точки сервера. Затем 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 партнеров по рекламным технологиям, API 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 см. в разделе Регистрация учетной записи Privacy Sandbox . (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]",
}

Ключи отладки позволяют получить дополнительную информацию об отчетах по атрибуции; Узнайте больше об их настройке.

Принимать агрегированные отчеты

Эта конечная точка должна быть доступна по URI. Дополнительные сведения о регистрации URI см. в разделе Регистрация учетной записи Privacy Sandbox . (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]"
}

Ключи отладки позволяют получить дополнительную информацию об отчетах по атрибуции; Узнайте больше об их настройке.

Настройка Android-клиента

Клиентское приложение регистрирует источники атрибуции и триггеры, а также позволяет создавать агрегированные отчеты на уровне событий и агрегированные отчеты. Чтобы подготовить клиентское устройство или эмулятор Android для использования API отчетов об атрибуции, выполните следующие действия:

  1. Настройте среду разработки для Privacy Sandbox на Android.
  2. Установите образ системы на поддерживаемое устройство или настройте эмулятор , включающий поддержку Privacy Sandbox на Android.
  3. Включите доступ к API отчетов об атрибуции, выполнив следующую команду ADB. (API отключен по умолчанию.)

    adb shell device_config put adservices ppapi_app_allow_list \"\*\"
  4. Если вы локально тестируете API отчетов по атрибуции (например, тестируете на устройстве, к которому у вас есть физический доступ), запустите эту команду, чтобы отключить регистрацию:

    adb shell device_config put adservices disable_measurement_enrollment_check "true"
  5. Включите разрешение ACCESS_ADSERVICES_ATTRIBUTION в файл манифеста Android и создайте конфигурацию рекламных служб для своего приложения, чтобы использовать 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 содержит методы, помогающие регистрировать события источников атрибуции и триггеры конверсий .

Регистрация события источника атрибуции

Когда объявление просматривается или нажимается на него, приложение издателя вызывает registerSource() чтобы зарегистрировать источник атрибуции, как показано во фрагменте кода.

API отчетов по атрибуции поддерживает следующие типы событий источников атрибуции:

  • Клики, которые вы обычно регистрируете с помощью метода обратного вызова, аналогичного onClick() . Соответствующее триггерное событие обычно происходит вскоре после события щелчка. Этот тип событий предоставляет больше информации о взаимодействии с пользователем и, следовательно, является хорошим источником атрибуции, которому можно придать высокий приоритет.
  • Представления, которые вы обычно регистрируете в методе обратного вызова, аналогичном onAdShown() . Соответствующее триггерное событие может произойти через несколько часов или дней после события просмотра.

Котлин

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)

Ява

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 и заголовок «Местоположение» со следующим URL-адресом, который нужно посетить для дополнительной регистрации.

В цепочке используется только поле «назначение», указанное при первом посещении. Количество посещений имеет тот же лимит, что и заголовки «Атрибуция-Отчетность-Перенаправление». Эта поддержка перенаправления дополняет существующую поддержку «Атрибуция-Отчетность-Перенаправление», и если оба они присутствуют, предпочтение отдается «Атрибуции-Отчетности-Перенаправлению».

Регистрация события-триггера конверсии

Чтобы зарегистрировать событие триггера конверсии, вызовите registerTrigger() в своем приложении:

Котлин

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)

Ява

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, который браузер выполняет для регистрации источника атрибуции перед тем, как направить пользователя в приложение:

Котлин

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)

Ява

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, который браузер выполняет для регистрации конверсии после того, как пользователь был направлен из приложения:

Котлин

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)

Ява

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

При измерении атрибуции между приложением и сайтом, где источники могут стимулировать конверсию как в приложение, так и в веб-адресах, в отчетах на уровне событий можно указать, произошел ли триггер в приложении или на сайте. Чтобы компенсировать эту дополнительную детализацию, генерируемые отчеты с шумом составляют до ~7x для кликов и ~1,7x для просмотров.

Некоторым рекламным технологиям не требуются отчеты на уровне событий, чтобы указать, произошел ли триггер в приложении или в веб-адресе. Специалисты по рекламе могут использовать 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
  }

Формировать и доставлять отчеты

API отчетов об атрибуции отправляет отчеты на конечные точки на вашем сервере, которые принимают отчеты на уровне событий и агрегированные отчеты .

Принудительное выполнение заданий отчетов

После регистрации события источника атрибуции или события-триггера система планирует запуск задания создания отчетов. По умолчанию это задание выполняется каждые 4 часа. В целях тестирования вы можете принудительно запускать задания отчетов или сократить интервалы между заданиями.

Принудительное выполнение задания атрибуции:

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);
});

Тестирование

Чтобы помочь вам начать работу с API отчетов по атрибуции, вы можете использовать проект MeasurementSampleApp на GitHub. В этом примере приложения демонстрируется регистрация источника атрибуции и регистрация триггера.

Для конечных точек сервера рассмотрите следующие справочные ресурсы или собственное решение:

  • MeasurementAdTechServerSpec включает определения сервисов OpenAPI, которые можно развернуть на поддерживаемых платформах макетов или микросервисов.
  • MeasurementAdTechServer включает эталонную реализацию макета сервера на основе приложения Spring Boot для Google App Engine.

Предварительные условия

Развертывайте фиктивные API на удаленных конечных точках, доступных с вашего тестового устройства или эмулятора. Для простоты тестирования обратитесь к примерам проектов MeasurementAdTechServerSpec и MeasurementAdTechServer .

Функциональность для тестирования

  • Используйте регистрацию источника атрибуции и триггера конверсии. Убедитесь, что конечные точки на стороне сервера отвечают в правильном формате.
  • Выполнение заданий по отчетности .
  • Проверьте доставку отчетов на серверной части или консоли вашего тестового сервера.

Будущие функции

Гибкая настройка на уровне событий

Конфигурация по умолчанию для отчетов на уровне событий рекомендуется для начала тестирования утилиты, но может не подойти для всех случаев использования. API отчетов по атрибуции будет поддерживать дополнительные, более гибкие конфигурации, чтобы специалисты по рекламе могли лучше контролировать структуру своих отчетов на уровне событий и максимизировать полезность данных. Эта дополнительная гибкость будет реализована в 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 ). Обратите внимание, что триггер по-прежнему может сопоставляться с источником агрегируемых отчетов, несмотря на несоответствие двум критериям соответствия, упомянутым ранее.
  • Специальный алгоритм суммирования и группирования всех триггеров в окне атрибуции. Это позволяет триггерам указывать параметр 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, количество данных триггера, #windows) для увеличения другого, чтобы сохранить уровень шума
  • уменьшение некоторых размеров конфигурации по умолчанию (#triggers, количество данных триггера, #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 дней + 1 час:

// 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 дней + 1 час:

// 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 установлено значение count. Если зарегистрировано 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, и мы будем обновлять это объяснение примерами конфигураций для этих вариантов использования.

Межсетевая атрибуция без редиректов

Специалистам по рекламе следует использовать перенаправления для регистрации нескольких триггеров источников атрибуции и выполнения межсетевой атрибуции. Эта функция помогает поддерживать межсетевую атрибуцию, когда перенаправление между сетями невозможно. Узнать больше .

Рекламные специалисты могут отправлять конфигурацию в ответе на регистрацию триггера на основе того, какие источники, зарегистрированные другими рекламными специалистами, выбраны для создания производных источников; эти производные источники затем используются для атрибуции. Агрегированные отчеты создаются, если триггер приписывается производному источнику. Генерация отчетов о событиях для производных источников не поддерживается.

Рекламные специалисты могут выбирать из 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 см. в примечаниях к выпуску .

Сообщайте об ошибках и проблемах

Ваш отзыв – важная часть Privacy Sandbox на Android! Сообщайте нам о любых обнаруженных проблемах или идеях по улучшению Privacy Sandbox на Android.

,
для разработчиков

Читая документацию Privacy Sandbox для Android, используйте кнопку Developer Preview или Beta , чтобы выбрать версию программы, с которой вы работаете, поскольку инструкции могут отличаться.


API отчетов по атрибуции предназначен для обеспечения улучшенной конфиденциальности пользователей за счет устранения зависимости от межпартийных идентификаторов пользователей, а также для поддержки ключевых вариантов использования для атрибуции и измерения конверсий в приложениях. В этом руководстве разработчика описывается, как настроить и протестировать API отчетов по атрибуции для регистрации кликов, просмотров и конверсий объявлений путем вызова методов, которые регистрируют соответствующие триггеры и источники для таких событий.

В этом руководстве вы узнаете, как настроить конечные точки сервера и создать клиентское приложение, вызывающее эти службы. Подробную информацию об общей конструкции API отчетов об атрибуции можно найти в предложении по дизайну .

Ключевые термины

  • Источниками атрибуции являются клики или просмотры.
  • Триггеры — это события, которые можно отнести к конверсиям.
  • Отчеты содержат данные о триггере и соответствующем источнике атрибуции. Эти отчеты отправляются в ответ на триггерные события. API отчетов по атрибуции поддерживает отчеты на уровне событий и агрегированные отчеты .

Прежде чем начать

Чтобы использовать API отчетов по атрибуции, выполните задачи на стороне сервера и на стороне клиента, перечисленные в следующих разделах.

Настройка конечных точек API отчетов по атрибуции

Для API отчетов по атрибуции требуется набор конечных точек, к которым вы можете получить доступ с тестового устройства или эмулятора. Создайте одну конечную точку для каждой из следующих задач на стороне сервера:

Существует несколько способов настройки необходимых конечных точек:

  • Самый быстрый способ приступить к работе — развернуть определения служб OpenAPI v3 из нашего репозитория примеров кода на макетной платформе или платформе микросервисов. Вы можете использовать Postman , Prism или любую другую платформу макетного сервера, которая принимает этот формат. Разверните каждую конечную точку и отслеживайте URI для использования в вашем приложении. Чтобы проверить доставку отчета, обратитесь к ранее выполненным вызовам к макетной или бессерверной платформе.
  • Запустите собственный автономный сервер, используя образец Kotlin на основе Spring Boot . Разверните этот сервер у своего облачного провайдера или во внутренней инфраструктуре.
  • Используйте определения служб в качестве примеров для интеграции конечных точек в существующую систему.

Принять регистрацию источника

Эта конечная точка должна быть адресована через URI, подобный следующему:

https://adtech.example/attribution_source

Когда клиентское приложение регистрирует источник атрибуции , оно предоставляет URI для этой конечной точки сервера. Затем 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 партнеров по рекламным технологиям, API 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 для этой конечной точки сервера. Затем 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 партнеров по рекламным технологиям, API 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 см. в разделе Регистрация учетной записи Privacy Sandbox . (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]",
}

Ключи отладки позволяют получить дополнительную информацию об отчетах по атрибуции; Узнайте больше об их настройке.

Принимать агрегированные отчеты

Эта конечная точка должна быть доступна по URI. Дополнительные сведения о регистрации URI см. в разделе Регистрация учетной записи Privacy Sandbox . (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]"
}

Ключи отладки позволяют получить дополнительную информацию об отчетах по атрибуции; Узнайте больше об их настройке.

Настройка Android-клиента

Клиентское приложение регистрирует источники атрибуции и триггеры, а также позволяет создавать агрегированные отчеты на уровне событий и агрегированные отчеты. Чтобы подготовить клиентское устройство или эмулятор Android для использования API отчетов об атрибуции, выполните следующие действия:

  1. Настройте среду разработки для Privacy Sandbox на Android.
  2. Установите образ системы на поддерживаемое устройство или настройте эмулятор , включающий поддержку Privacy Sandbox на Android.
  3. Включите доступ к API отчетов об атрибуции, выполнив следующую команду ADB. (API отключен по умолчанию.)

    adb shell device_config put adservices ppapi_app_allow_list \"\*\"
  4. Если вы локально тестируете API отчетов по атрибуции (например, тестируете на устройстве, к которому у вас есть физический доступ), запустите эту команду, чтобы отключить регистрацию:

    adb shell device_config put adservices disable_measurement_enrollment_check "true"
  5. Включите разрешение ACCESS_ADSERVICES_ATTRIBUTION в файл манифеста Android и создайте конфигурацию рекламных служб для своего приложения, чтобы использовать 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 содержит методы, помогающие регистрировать события источников атрибуции и триггеры конверсий .

Регистрация события источника атрибуции

Когда объявление просматривается или нажимается на него, приложение издателя вызывает registerSource() чтобы зарегистрировать источник атрибуции, как показано во фрагменте кода.

API отчетов по атрибуции поддерживает следующие типы событий источников атрибуции:

  • Клики, которые вы обычно регистрируете с помощью метода обратного вызова, аналогичного onClick() . Соответствующее триггерное событие обычно происходит вскоре после события щелчка. Этот тип событий предоставляет больше информации о взаимодействии с пользователем и, следовательно, является хорошим источником атрибуции, которому можно придать высокий приоритет.
  • Представления, которые вы обычно регистрируете в методе обратного вызова, аналогичном onAdShown() . Соответствующее триггерное событие может произойти через несколько часов или дней после события просмотра.

Котлин

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)

Ява

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 и заголовок «Местоположение» со следующим URL-адресом, который нужно посетить для дополнительной регистрации.

В цепочке используется только поле «назначение», указанное при первом посещении. Количество посещений имеет тот же лимит, что и заголовки «Атрибуция-Отчетность-Перенаправление». Эта поддержка перенаправления дополняет существующую поддержку «Атрибуция-Отчетность-Перенаправление», и если оба они присутствуют, предпочтение отдается «Атрибуции-Отчетности-Перенаправлению».

Регистрация события-триггера конверсии

Чтобы зарегистрировать событие триггера конверсии, вызовите registerTrigger() в своем приложении:

Котлин

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)

Ява

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, который браузер выполняет для регистрации источника атрибуции перед тем, как направить пользователя в приложение:

Котлин

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)

Ява

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, который браузер выполняет для регистрации конверсии после того, как пользователь был направлен из приложения:

Котлин

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)

Ява

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

При измерении атрибуции между приложением и сайтом, где источники могут стимулировать конверсию как в приложение, так и в веб-адресах, в отчетах на уровне событий можно указать, произошел ли триггер в приложении или на сайте. Чтобы компенсировать эту дополнительную детализацию, генерируемые отчеты с шумом составляют до ~7x для кликов и ~1,7x для просмотров.

Некоторым рекламным технологиям не требуются отчеты на уровне событий, чтобы указать, произошел ли триггер в приложении или в веб-адресе. Специалисты по рекламе могут использовать 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
  }

Формировать и доставлять отчеты

API отчетов об атрибуции отправляет отчеты на конечные точки на вашем сервере, которые принимают отчеты на уровне событий и агрегированные отчеты .

Принудительное выполнение заданий отчетов

После регистрации события источника атрибуции или события-триггера система планирует запуск задания создания отчетов. По умолчанию это задание выполняется каждые 4 часа. В целях тестирования вы можете принудительно запускать задания отчетов или сократить интервалы между заданиями.

Принудительное выполнение задания атрибуции:

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);
});

Тестирование

Чтобы помочь вам начать работу с API отчетов по атрибуции, вы можете использовать проект MeasurementSampleApp на GitHub. В этом примере приложения демонстрируется регистрация источника атрибуции и регистрация триггера.

Для конечных точек сервера рассмотрите следующие справочные ресурсы или собственное решение:

  • MeasurementAdTechServerSpec включает определения сервисов OpenAPI, которые можно развернуть на поддерживаемых платформах макетов или микросервисов.
  • MeasurementAdTechServer включает эталонную реализацию макета сервера на основе приложения Spring Boot для Google App Engine.

Предварительные условия

Развертывайте фиктивные API на удаленных конечных точках, доступных с вашего тестового устройства или эмулятора. Для простоты тестирования обратитесь к примерам проектов MeasurementAdTechServerSpec и MeasurementAdTechServer .

Функциональность для тестирования

  • Используйте регистрацию источника атрибуции и триггера конверсии. Убедитесь, что конечные точки на стороне сервера отвечают в правильном формате.
  • Выполнение заданий по отчетности .
  • Проверьте доставку отчетов на серверной части или консоли вашего тестового сервера.

Будущие функции

Гибкая настройка на уровне событий

Конфигурация по умолчанию для отчетов на уровне событий рекомендуется для начала тестирования утилиты, но может не подойти для всех случаев использования. API отчетов по атрибуции будет поддерживать дополнительные, более гибкие конфигурации, чтобы специалисты по рекламе могли лучше контролировать структуру своих отчетов на уровне событий и максимизировать полезность данных. Эта дополнительная гибкость будет реализована в 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 ). Обратите внимание, что триггер по-прежнему может сопоставляться с источником агрегируемых отчетов, несмотря на несоответствие двум критериям соответствия, упомянутым ранее.
  • Специальный алгоритм суммирования и группирования всех триггеров в окне атрибуции. Это позволяет триггерам указывать параметр 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, количество данных триггера, #windows) для увеличения другого, чтобы сохранить уровень шума
  • уменьшение некоторых размеров конфигурации по умолчанию (#triggers, количество данных триггера, #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 дней + 1 час:

// 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 дней + 1 час:

// 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 установлено значение count. Если зарегистрировано 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, и мы будем обновлять это объяснение примерами конфигураций для этих вариантов использования.

Межсетевая атрибуция без редиректов

Специалистам по рекламе следует использовать перенаправления для регистрации нескольких триггеров источников атрибуции и выполнения межсетевой атрибуции. Эта функция помогает поддерживать межсетевую атрибуцию, когда перенаправление между сетями невозможно. Узнать больше .

Рекламные специалисты могут отправлять конфигурацию в ответе на регистрацию триггера на основе того, какие источники, зарегистрированные другими рекламными специалистами, выбраны для создания производных источников; эти производные источники затем используются для атрибуции. Агрегированные отчеты создаются, если триггер приписывается производному источнику. Генерация отчетов о событиях для производных источников не поддерживается.

Рекламные специалисты могут выбирать из 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 см. в примечаниях к выпуску .

Сообщайте об ошибках и проблемах

Ваш отзыв – важная часть Privacy Sandbox на Android! Сообщайте нам о любых обнаруженных проблемах или идеях по улучшению Privacy Sandbox на Android.