Guia para desenvolvedores da API Attribution Reporting

Ao ler a documentação do Sandbox de privacidade do Android, use o botão Prévia para desenvolvedores ou Beta para selecionar a versão do programa com que você está trabalhando, porque as instruções podem variar.


Enviar feedback

A API Attribution Reporting foi projetada para melhorar a privacidade ao remover a dependência de identificadores do usuário entre diferentes partes e também para oferecer suporte aos principais casos de uso de medição de conversões e atribuição em apps. Neste guia para desenvolvedores, descrevemos como configurar e testar as APIs Attribution Reporting para registrar cliques, visualizações e conversões de anúncios chamando métodos que registram os acionadores e as origens relevantes para esses eventos.

Neste guia, você vai aprender a configurar endpoints do servidor e criar um app cliente para chamar esses serviços. Saiba mais sobre o design geral da API Attribution Reporting na proposta de design.

Termos-chave

  • Fontes de atribuição se referem a cliques ou visualizações.
  • Acionadores são eventos que podem ser atribuídos a conversões.
  • Relatórios contêm dados sobre um acionador e a fonte de atribuição correspondente. Eles são enviados em resposta a eventos acionadores. A API Attribution Reporting oferece suporte a relatórios de eventos e relatórios agregáveis.

Antes de começar

Para usar a API Attribution Reporting, conclua as tarefas do lado do servidor e do cliente listadas nas próximas seções.

Configurar endpoints da API Attribution Reporting

A API Attribution Reporting exige um conjunto de endpoints que podem ser acessados em um dispositivo ou emulador de teste. Crie um endpoint para cada uma das tarefas do lado do servidor abaixo:

Há vários métodos para configurar os endpoints necessários:

  • A forma de adoção mais rápida é implantar as definições do serviço OpenAPI v3 usando nosso repositório de exemplo de código em uma plataforma de simulação ou microsserviços. É possível usar o Postman, Prism (links em inglês) ou qualquer outra plataforma de servidor simulado que aceite esse formato. Implante cada endpoint e acompanhe os URIs para uso no app. Se quiser verificar a entrega de relatórios, consulte as chamadas feitas anteriormente para a plataforma de simulação ou sem servidor.
  • Execute seu próprio servidor autônomo usando o exemplo em Kotlin com base em Spring Boot (links em inglês). Implante o servidor no provedor de nuvem ou na infraestrutura interna.
  • Use as definições de serviço como exemplos para integrar os endpoints ao sistema atual.

Aceitar registros da fonte

O endpoint precisa ser endereçável de um URI semelhante a este:

https://adtech.example/attribution_source

Quando um app cliente registra uma fonte de atribuição, ele fornece o URI para esse endpoint do servidor. Em seguida, a API Attribution Reporting faz uma solicitação e inclui um dos cabeçalhos abaixo:

  • Para eventos de clique:

    Attribution-Reporting-Source-Info: navigation
    
  • Para eventos de visualização:

    Attribution-Reporting-Source-Info: event
    

As solicitações de registro de fonte do DP 10 e versões mais recentes vão conter um cabeçalho "Version" da seguinte maneira:

  Version: 202310

Configure o endpoint do servidor para responder com o seguinte:

// 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>

Confira um caso com exemplos de valores adicionados:

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

Se Attribution-Reporting-Redirects tiver URIs de parceiros de adtech, a API Attribution Reporting vai fazer uma solicitação semelhante para cada URI. Cada parceiro de tecnologias de publicidade precisa configurar um servidor que responda com os cabeçalhos abaixo:

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.

Aceitar registros de acionadores de conversões

O endpoint precisa ser endereçável de um URI semelhante a este:

https://adtech.example/attribution_trigger

Quando um app cliente registra um evento acionador, ele fornece o URI para esse endpoint do servidor. Em seguida, a API Attribution Reporting faz uma solicitação ao URI informado.

As solicitações de registro de acionadores do DP 10 e versões mais recentes vão conter um cabeçalho "Version" da seguinte maneira:

  Version: 202310

Configure o endpoint do servidor para responder com o seguinte:

// 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>

Confira um caso com exemplos de valores adicionados:

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

Existe um limite de 25 bytes por ID de chave de agregação e string de filtro. Isso significa que os IDs das chaves de agregação e as strings de filtro não podem ter mais que 25 caracteres. No exemplo, campaignCounts tem 14 caracteres, portanto, é um ID de chave de agregação válido, e 1234 tem 4 caracteres, portanto, é uma string de filtro válida. Se um ID de chave de agregação ou uma string de filtro tiver mais que 25 caracteres, o acionador será ignorado.

Se Attribution-Reporting-Redirect tiver URIs de parceiros de tecnologias de publicidade, a API Attribution Reporting vai fazer uma solicitação semelhante para cada URI. Cada parceiro de tecnologias de publicidade precisa configurar um servidor que responda com os cabeçalhos abaixo:

// 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.

Aceitar relatórios de eventos

O endpoint precisa ser endereçável de um URI. Consulte Registrar uma conta do Sandbox de privacidade para saber mais sobre como registrar URIs. O URI é inferido da origem dos servidores usados para aceitar os registros da fonte e do acionador. Usando os URIs de exemplo para endpoints que aceitam registros da fonte e registros do acionador, o URI do endpoint é:

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

As solicitações de relatórios de evento do DP 10 e versões mais recentes vão conter um cabeçalho de versão da seguinte maneira:

  Version: 202310

Configure esse servidor para aceitar solicitações JSON que usem o formato abaixo:

{
  "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]",
}

As chaves de depuração oferecem mais insights sobre seus Relatórios de atribuição. Aprenda como as configurar.

Aceitar relatórios agregáveis

O endpoint precisa ser endereçável de um URI. Consulte Registrar uma conta do Sandbox de privacidade para saber mais sobre como registrar URIs. O URI é inferido da origem dos servidores usados para aceitar os registros da fonte e do acionador. Usando os URIs de exemplo para endpoints que aceitam registros da fonte e registros do acionador, o URI do endpoint é:

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

As solicitações de relatórios agregáveis do DP 10 e versões mais recentes vão ter um cabeçalho Version desta maneira:

  Version: 202310

Os campos criptografados e não criptografados são preenchidos para relatórios agregáveis. Os relatórios criptografados permitem que você comece a testar com o serviço de agregação, enquanto o campo não criptografado fornece insights sobre a forma como os pares de chave-valor definidos estão estruturando os dados.

Configure esse servidor para aceitar solicitações JSON que usem o formato abaixo:

{
  // 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]"
}

As chaves de depuração oferecem mais insights sobre seus Relatórios de atribuição. Aprenda como as configurar.

Configurar o cliente Android

O app cliente registra fontes e acionadores de atribuição e ativa a geração de relatórios agregáveis e de evento. Para preparar um dispositivo ou emulador com cliente Android para usar a API Attribution Reporting, siga estas etapas:

  1. Configure seu ambiente de desenvolvimento para o Sandbox de privacidade do Android.
  2. Instale uma imagem do sistema em um dispositivo com suporte ou configure um emulador que tenha suporte ao Sandbox de privacidade do Android.
  3. Ative o acesso à API Attribution Reporting executando o comando ADB abaixo. A API fica desativada por padrão.

    adb shell device_config put adservices ppapi_app_allow_list \"\*\"
    
  4. Se você estiver testando localmente a API Attribution Reporting (por exemplo, em um dispositivo a que você tem acesso físico), execute este comando para desativar a inscrição:

    adb shell device_config put adservices disable_measurement_enrollment_check "true"
    
  5. Inclua a permissão ACCESS_ADSERVICES_ATTRIBUTION no seu arquivo de manifesto do Android e crie uma configuração de serviços de anúncios para seu app usar as APIs Attribution Reporting:

    <uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION" />
    
  6. Opcional: se você planeja receber relatórios de depuração, inclua a permissão ACCESS_ADSERVICES_AD_ID no arquivo de manifesto do Android.

    <uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" />
    
  7. Faça referência a uma configuração de serviços de publicidade no elemento <application> do manifesto:

    <property android:name="android.adservices.AD_SERVICES_CONFIG"
              android:resource="@xml/ad_services_config" />
    
  8. Especifique o recurso XML dos serviços de publicidade referenciados no manifesto, como res/xml/ad_services_config.xml. Saiba mais sobre as permissões dos serviços de anúncios e o controle de acesso do SDK.

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

Registrar eventos de anúncio

Seu app precisa registrar origens e conversões à medida que elas ocorrem para garantir que sejam informadas corretamente. A classe MeasurementManager apresenta métodos para ajudar a registrar eventos da fonte de atribuição e acionadores de conversão.

Registrar um evento de fonte de atribuição

Quando um anúncio é visualizado ou clicado, um app editor chama registerSource() para registrar uma fonte de atribuição conforme mostrado no snippet de código.

A API Attribution Reporting oferece suporte a estes tipos de eventos da fonte de atribuição:

  • Cliques, que você normalmente registra em um método de callback semelhante a onClick(). O evento acionador correspondente geralmente ocorre logo após o evento de clique. Esse tipo de evento oferece mais informações sobre a interação do usuário e é um bom tipo de fonte de atribuição para se priorizar.
  • Visualizações, que você normalmente registra em um método de callback semelhante a onAdShown(). O evento acionador correspondente pode ocorrer horas ou dias após o evento de visualização.

Kotlin

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

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

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

val future = CompletableFuture<Void>()

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

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

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

Java

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

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

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

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

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

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

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

Após o registro, a API emite uma solicitação HTTP POST ao endpoint do serviço no endereço especificado pelo attributionSourceUri. A resposta do endpoint inclui valores para destination, source_event_id, expiry e source_priority.

Se as adtechs de origem quiserem compartilhar registros da fonte, o URI da fonte de atribuição original vai poder incluir redirecionamentos para outros endpoints de adtechs. Os limites e as regras aplicáveis aos redirecionamentos estão detalhados na proposta técnica.

Agora, existe suporte para os redirecionamentos em cadeia para registerSource e registerTrigger. Além do cabeçalho de registro, o consumidor da API agora pode fornecer um redirecionamento HTTP como resposta do servidor, que inclui um código de status 302 e um cabeçalho "Location" com o próximo URL a ser visitado para registro.

Apenas o campo "destination", fornecido na primeira visita, é usado em toda a cadeia. O limite de número de visitas é igual ao dos cabeçalhos "Attribution-Reporting-Redirect". O suporte ao redirecionamento é adicionado ao "Attribution-Reporting-Redirect" já existente. Se ambos estiverem presentes, "Attribution-Reporting-Redirect" tem preferência.

Registrar um evento acionador de conversão

Para registrar um evento acionador de conversão, chame o método registerTrigger() no app:

Kotlin

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

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

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

val future = CompletableFuture<Void>()

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

Java

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

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

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

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

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

Após o registro, a API emite uma solicitação HTTP POST ao endpoint do serviço no endereço especificado pelo attributionTriggerUri. A resposta do endpoint inclui valores para relatórios agregados e de eventos.

Se a plataforma de tecnologias de publicidade de origem permitir que os registros de acionamento sejam compartilhados, o URI vai poder incluir redirecionamentos para URIs que pertençam a outras plataformas de tecnologias de publicidade. Os limites e as regras aplicáveis aos redirecionamentos estão detalhados na proposta técnica.

Registrar a medição em apps e na Web

No caso em que um app e um navegador desempenham um papel na jornada do usuário da origem para o acionador, há diferenças sutis na implementação do registro de eventos de anúncios. Se um usuário vir um anúncio em um app e for redirecionado para um navegador de uma conversão, a origem será registrada pelo app e a conversão pelo navegador da Web. Da mesma forma, se um usuário começa em um navegador da Web e é direcionado a um app para conversão, o navegador registra a origem, e o app registra a conversão.

Como há diferenças na forma como as adtechs são organizadas na Web e no Android, adicionamos novas APIs para registrar origens e acionadores quando elas são executadas em navegadores. A principal diferença entre essas APIs e as APIs com base em apps correspondentes é que esperamos que o navegador siga os redirecionamentos, aplique qualquer filtro específico do navegador e transmita os registros válidos para a plataforma chamando registerWebSource() ou registerWebTrigger().

O snippet de código abaixo mostra um exemplo da chamada de API que o navegador faz para registrar uma fonte de atribuição antes de direcionar o usuário para um app:

Kotlin

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

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

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

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

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

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

val future = CompletableFuture<Void>()

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

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

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

Java

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

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

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

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

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

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

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

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

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

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

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

O snippet de código abaixo mostra um exemplo da chamada de API que o navegador faz para registrar uma conversão depois que o usuário é direcionado do app:

Kotlin

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

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

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

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

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

val future = CompletableFuture<Void>()

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

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

Java

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

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

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

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

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

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

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

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

Adicionar ruído para manter a privacidade

Os relatórios de evento contêm o destino, o ID da fonte de atribuição e os dados do acionador. Eles são enviados no formato original (não criptografado) para a origem do relatório. Para proteger a privacidade do usuário, o ruído pode ser adicionado para dificultar a identificação de um usuário individual. Esses relatórios são gerados e enviados de acordo com o framework de diferencial de privacidade. Estes são os valores padrão de porcentagem de ruído para diferentes cenários:

Tipo de origem

Valor de destino da origem

Probabilidade de relatório notificado por registro de origem

Iniciar

App ou Web

0,0000025

Iniciar

App e Web

0,0000042

Clique

App ou Web

0,0024263

Clique

App e Web

0,0170218

Na medição de atribuição de app para Web, em que as origens podem gerar conversões para destinos de apps e da Web, os relatórios de eventos podem especificar se o acionador ocorreu no app ou na Web. Para compensar esse detalhe adicional, os relatórios geram aproximadamente 7 vezes mais cliques e 1,7 vezes mais visualizações.

Algumas adtechs não exigem relatórios no nível do evento para especificar se o acionador ocorreu no app ou no destino da Web. As adtechs podem usar o campo coarse_event_report_destinations no cabeçalho Attribution-Reporting-Register-Source para reduzir o ruído. Se uma origem com o campo coarse_event_report_destinations especificado ganhar a atribuição, o relatório resultante vai incluir destinos da Web e do app sem distinção de onde o acionador real ocorreu.

Nos exemplos a seguir, um usuário clica em um anúncio, e essa origem é registrada com a API. Em seguida, o usuário faz a conversão no app e no site do anunciante. Essas duas conversões são registradas como acionadores e atribuídas ao clique inicial.

Um cabeçalho HTTP de registro de origem baseado em clique:

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"
}

Um gatilho é registrado no app com o nome do pacote com.advertiser.example:

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

Um acionador é registrado em um navegador no site com o domínio eTLD+1 https://advertiser.com:

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

Os relatórios de evento resultantes são gerados. Supondo que os dois acionadores sejam atribuídos à origem, os seguintes relatórios no nível do evento são gerados:

  {
    "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
  }

Gerar e mostrar relatórios

A API Attribution Reporting envia relatórios aos endpoints no servidor que aceitam relatórios agregáveis e de eventos.

Forçar a execução de jobs de geração de relatórios

Depois de registrar um evento da fonte de atribuição ou de acionador, o sistema agenda a execução do job de geração de relatórios. Por padrão, esse job é executado a cada quatro horas. Para fins de teste, é possível forçar a execução dos jobs de geração de relatórios ou reduzir os intervalos entre eles.

Forçar a execução do job de atribuição:

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

Forçar a execução do job de geração de relatórios de eventos:

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

Forçar a execução do job de geração de relatórios agregáveis:

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

Confira a saída no logcat para saber quando os jobs foram executados. Ela vai ser semelhante a esta:

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

Forçar o envio de relatórios

Mesmo que a execução do job de geração de relatórios seja forçada, o sistema ainda envia relatórios de acordo com os tempos de envio programados, que variam de algumas horas a vários dias. Para fins de teste, é possível adiantar o tempo do sistema do dispositivo para que ele seja maior que os atrasos programados para iniciar o envio do relatório.

Verificar relatórios no seu servidor

Depois que os relatórios forem enviados, verifique o recebimento conferindo os relatórios recebidos, os registros de servidor aplicáveis, como o histórico do servidor simulado, ou seu sistema personalizado.

Decodificar o relatório agregado

Uma versão não criptografada do relatório agregado recebido é mantida no campo debug_cleartext_payload. Apesar de não ser criptografada, essa versão ainda precisa ser decodificada.

Veja abaixo um exemplo de decodificação do conteúdo do campo debug_cleartext_payload em duas etapas: a primeira usando a decodificação Base 64 e a segunda, a decodificação 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);
});

Testes

Para ajudar você a usar a API Attribution Reporting, use o projeto MeasurementSampleApp (link em inglês) no GitHub. Esse app de exemplo mostra o registro da fonte de atribuição e o registro do acionador.

Para endpoints do servidor, considere os recursos de referência abaixo ou uma solução personalizada própria:

  • O MeasurementAdTechServerSpec (link em inglês) inclui definições de serviço da OpenAPI, que podem ser implantadas em plataformas de simulação ou microsserviços com suporte.
  • O MeasurementAdTechServer (link em inglês) inclui uma implementação de referência de um servidor simulado com base no app Spring Boot para o Google App Engine.

Pré-requisitos

Implante APIs simuladas em endpoints remotos acessíveis pelo emulador ou dispositivo de teste. Para facilitar os testes, consulte os exemplos de projeto MeasurementAdTechServerSpec e MeasurementAdTechServer (links em inglês).

Testar funcionalidades

Próximos recursos

Configuração flexível no nível do evento

A configuração padrão para relatórios de eventos é recomendada para iniciar testes utilitários, mas pode não ser ideal para todos os casos de uso. A API Attribution Reporting vai oferecer suporte opcional, configurações mais flexíveis para que as adtechs tenham mais controle sobre a estrutura dos relatórios de eventos e possam maximizar os utilitários dos dados. Essa flexibilidade extra será introduzida na API Attribution Reporting em duas fases:

  • Fase 1: configuração parcialmente flexível no nível do evento, um subconjunto da Fase 2.
  • Fase 2: configuração completamente flexível no nível do evento.

Fase 1: nível do evento parcialmente flexível

Vamos adicionar os dois parâmetros opcionais abaixo ao JSON em 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>, ...]
  }
}

Exemplo de configuração personalizada

Este exemplo de configuração oferece suporte a um desenvolvedor que quer otimizar para receber relatórios em janelas de relatórios anteriores.

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

Fase 2: nível do evento totalmente flexível

Além dos parâmetros adicionados na Fase 1, vamos adicionar outro parâmetro opcional trigger_specs ao JSON em 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>, ...]
  }
}

Esta configuração especifica totalmente o espaço de saída dos relatórios de eventos por registro de origem. Para cada especificação de acionador, especificamos totalmente:

  • Um conjunto de critérios de correspondência:
    • A quais dados do acionador essa especificação se aplica. Essa origem está qualificada para corresponder apenas aos acionadores que têm um dos valores de trigger_data especificados em trigger_specs. Em outras palavras, se o acionador corresponder a essa origem, mas trigger_data não for um dos valores na configuração da origem, o acionador vai ser ignorado.
    • Quando um acionador específico corresponde a essa especificação (usando event_report_windows). O acionador ainda pode corresponder a uma fonte para relatórios agregáveis mesmo com falhas nos dois critérios de correspondência mencionados anteriormente.
  • Um algoritmo específico para resumir e agrupar todos os acionadores em uma janela de atribuição. Isso permite que os acionadores especifiquem um parâmetro value que é somado para uma especificação específica, mas informado como um valor agrupado.

Os gatilhos também oferecem suporte à adição de um parâmetro de valor opcional nos dicionários em event_trigger_data.

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

Cada registro de acionador vai corresponder a, no máximo, uma especificação e vai atualizar o valor de resumo associado. De modo geral, no momento do acionamento, vamos:

  • Aplicar filtros de atribuição globais.
  • Para cada especificação de acionador, avalie o event_trigger_data na especificação para encontrar uma correspondência usando a event_reporting_window da especificação. As event_reporting_windows de nível superior atuam como um valor padrão caso alguma especificação de acionador seja o subcampo event_report_windows ausente.
  • A primeira especificação correspondente é escolhida para atribuição, e o valor do resumo é incrementado em value.

Quando a event_report_window de uma especificação for concluída, vamos mapear o valor de resumo dela para um bucket e enviar um relatório de evento para cada incremento no bucket de resumo causado por valores de acionadores atribuídos. Os relatórios vêm com um campo extra, trigger_summary_bucket.

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

Configurações equivalentes à versão atual

Confira abaixo configurações equivalentes para as fontes de navegação e eventos atuais das APIs, respectivamente. Especialmente para origens de navegação, isso ilustra por que os níveis de ruído são tão altos em relação às origens de eventos para manter os mesmos valores de épsilon: as origens de navegação têm um espaço de saída muito maior.

É possível que várias configurações sejam equivalentes, já que alguns parâmetros podem ser definidos como padrão ou reduzidos.

Origens de eventos equivalentes
// 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>,
}
Origens de navegação equivalentes
// 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>,
}

Exemplos de configurações personalizadas

Abaixo estão algumas outras configurações fora dos padrões. Em todos esses exemplos, as compensações do desenvolvedor incluem:

  • Reduzir algumas dimensões da configuração padrão (#triggers, cardinalidade de dados de acionamento, #windows) para aumentar outra e preservar o nível de ruído.
  • Reduzir algumas dimensões da configuração padrão (#triggers, cardinalidade de dados do acionador, #windows) para reduzir o nível de ruído

Informar intervalos de valor do acionador

Esta configuração de exemplo oferece suporte para um desenvolvedor que quer otimizar dados de valor para apenas uma janela de relatório (por exemplo, sete dias), trocando menos janelas de relatórios por menos ruído. Neste exemplo, qualquer acionador que defina trigger_data como um valor diferente de 0 não está qualificado para a atribuição.

{
  "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]
  }],
}

Os gatilhos podem ser registrados com o conjunto de campos value, que são somados e agrupados. Por exemplo, se houver três acionadores em até sete dias de registros da origem com os valores 1, 3 e 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}] }

Os valores são somados a 8 e informados nos relatórios seguintes após sete dias + 1 hora:

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

Nos próximos sete dias, estes acionadores serão registrados:

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

Os valores são somados em 8 + 50 + 45 = 103. Isso gera estes relatórios após 14 dias + 1 hora:

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

// Report 3
{
  ...
  "trigger_summary_bucket": [100, MAX_INT]
}
Informar contagens de acionadores

Este exemplo mostra como um desenvolvedor pode configurar uma origem para receber uma contagem de acionadores de até 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]
  }],
}

Os acionadores atribuídos com trigger_data definido como 0 são contabilizados e limitados a 10. O valor do acionador é ignorado, já que o summary_window_operator está definido para contagem. Se quatro acionadores forem registrados e atribuídos à origem, o relatório vai ficar assim:

// 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]
}
Binário com relatórios mais frequentes

Esta configuração de exemplo oferece suporte para um desenvolvedor que quer saber se pelo menos uma conversão ocorreu nos primeiros 10 dias (independente do valor), mas quer receber relatórios em intervalos mais frequentes do que o padrão. De novo, neste exemplo, qualquer acionador que defina trigger_data como um valor diferente de 0 não está qualificado para atribuição. É por isso que esse caso de uso é chamado de binário.

{
  "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]
  }],
}
Variar as especificações do acionador de origem para origem
{
  "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
}

Incentivamos os desenvolvedores a sugerir diferentes casos de uso para essa extensão de API. Vamos atualizar este explicativo com exemplos de configurações para esses casos de uso.

Atribuição de várias redes sem redirecionamentos

As adtechs precisam usar redirecionamentos para registrar vários acionadores da fonte de atribuição e realizá-la em várias redes. Com esse recurso, fica mais fácil oferecer suporte à atribuição de várias redes em que os redirecionamentos não são viáveis em todas elas. Saiba mais.

As adtechs podem enviar a configuração na resposta de registro do acionador, com base nas origens registradas por outras adtechs selecionadas para gerar origens derivadas, que são usadas para atribuição. Os relatórios agregados vão ser gerados se o acionador for atribuído a uma origem derivada. Não há suporte para geração de relatórios de eventos para origens derivadas.

As adtechs podem escolher a aggregation_keys nas origens registradas que querem compartilhar com adtechs parceiras. Essas chaves podem ser declaradas no campo opcional shared_aggregation_keys, localizado no cabeçalho de registro da origem Attribution-Reporting-Register-Source:

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

As origens derivadas são geradas com base na configuração no cabeçalho de registro de acionador 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]"
    }
  ]

Confira uma versão com valores de exemplo adicionados:

  "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"
    }
  ]

Dois novos campos opcionais foram adicionados para acionar o cabeçalho de registro. Esses campos ativam o identificador de adtechs vencedor nas chaves de relatório que podem ser agregadas:

  • x_network_bit_mapping: ID de registro para o mapeamento de bits do identificador de adtechs
  • x_network_data: deslocamento (à esquerda) para a operação x_network_bit_mapping OU da adtech vencedora com a parte da chave do acionador
Exemplo:
"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
  }
  …
}

Confira o cálculo resultante do acionador de chave ao gerar um relatório para a origem da AdTechB:

  • key_piece: 0x400 (010000000000)
  • key_offset: 12
  • Valor de enrollment_id da AdtechB: 2 (010) (de x_network_bit_mapping)
  • Parte da chave do acionador resultante: 0x400 | 0x2 << 12 = 0x2400

Limitações

Para conferir uma lista de recursos em desenvolvimento para o SDK Runtime, consulte as notas da versão.

Informar bugs e problemas

Seu feedback é uma parte crucial do Sandbox de privacidade do Android. Avise nossa equipe sobre problemas encontrados ou ideias para melhorar o Sandbox de privacidade do Android.