Attribution Reporting API developer guide

The Attribution Reporting API is designed to provide improved user privacy by removing reliance on cross-party user identifiers, and to support key use cases for attribution and conversion measurement across apps. This developer guide describes how to configure and test the Attribution Reporting APIs to register ad clicks, views, and conversions by calling methods that register the relevant triggers and sources for such events.

This guide teaches you how to set up server endpoints and build a client app that calls these services. Learn more about the overall design of Attribution Reporting API in the design proposal.

Key Terms

  • Attribution sources refer to clicks or views.
  • Triggers are events that can be attributed to conversions.
  • Reports contain data about a trigger and the corresponding attribution source. These reports are sent in response to trigger events. The Attribution Reporting API supports event-level reports and aggregatable reports.

Before you begin

To use the Attribution Reporting API, complete the server-side and client-side tasks listed in the following sections.

Set up Attribution Reporting API endpoints

The Attribution Reporting API requires a set of endpoints that you can access from a test device or emulator. Create one endpoint for each of the following server-side tasks:

There are several methods of setting up the required endpoints:

  • The fastest way to get up and running is deploy the OpenAPI v3 service definitions from our sample code repository to a mock or microservices platform. You can use Postman, Prism, or any other mock server platform that accepts this format. Deploy each endpoint and keep track of the URIs for use in your app. To verify report delivery, refer to the calls previously made to the mock or serverless platform.
  • Run your own standalone server using the Spring Boot-based Kotlin sample. Deploy this server on your cloud provider or internal infrastructure.
  • Use the service definitions as examples to integrate the endpoints into your existing system.

Accept source registration

This endpoint should be addressable from a URI similar to the following:

https://adtech.example/attribution_source

When a client app registers an attribution source, it provides the URI for this server endpoint. The Attribution Reporting API then makes a request and includes one of the following headers:

Attribution-Reporting-Source-Info: navigation
Attribution-Reporting-Source-Info: event

Configure your server endpoint to respond with the following:

// Metadata associated with attribution source.
Attribution-Reporting-Register-Source: {
  "destination": "[app package name]",
  "source_event_id": "[64 bit unsigned integer]",
  "expiry": "[64 bit signed integer]",
  "source_priority": "[64 bit signed integer]"
}
// Attribution source metadata specifying histogram contributions in
// aggregate report.
Attribution-Reporting-Register-Aggregatable-Source:
[{
  "id": "[key name]",
  "key_piece": "[key piece value]",
 },
 ...
]
// Specify additional Adtech URLs to register this source with.
Attribution-Reporting-Redirects: <Ad tech partner URIs; comma-separated>

Here's an example with sample values added:

Attribution-Reporting-Register-Source: {
  "destination": "android-app://com.example.advertiser",
  "source_event_id": "234",
  "expiry": "60000",
  "source_priority": "5"
}
Attribution-Reporting-Register-Aggregatable-Source:
[{
// Generates a "0x159" key piece named (low order bits of the key) for the key
// named "campaignCounts".
  "id": "campaignCounts",
// User saw an ad from campaign 345 (out of 511).
  "key_piece": "0x159",
},
{
// Generates "0x5" key piece (low order bits of the key) for the "geoValue" key
  "id": "geoValue",
// Source-side geo region = 5 (US), out of a possible ~100 regions.
  "key_piece": "0x5",
}]

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

If Attribution-Reporting-Redirects contains URIs of ad tech partners, the Attribution Reporting API then makes a similar request to each URI. Each ad tech partner must configure a server that responds with these headers:

Attribution-Reporting-Register-Source:
{
  "destination": "[app package name]",
  "source_event_id": "[64 bit unsigned integer]",
  "expiry": "[64 bit signed integer]",
  "source_priority": "[64 bit signed integer]"
}
Attribution-Reporting-Register-Aggregatable-Source:
[{
  "id": "[key name]",
  "key_piece": "[key piece value]",
 },
 ...
]
// This response can't include the Attribution-Reporting-Redirects header.

Accept conversion trigger registration

This endpoint should be addressable from a URI similar to the following:

https://adtech.example/attribution_trigger

When a client app registers a conversion trigger event, it provides the URI for this server endpoint. The Attribution Reporting API then makes a request to the provided URI.

Configure your server endpoint to respond with the following:

// Metadata associated with trigger.
Attribution-Reporting-Register-Event-Trigger:
[{
    // 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]",
    "trigger_priority": "[signed 64-bit integer]",
    "deduplication_key": "[signed 64-bit integer]"
}]

// Specify a list of dictionaries that generates aggregation keys.
Attribution-Reporting-Register-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"]]
  },
  ..
]
// 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.
Attribution-Reporting-Register-Aggregatable-Values:
[
   // Each source event can contribute a maximum of L1 = 2^16 to the aggregate
   // histogram.
  {
   "[key_name]": [value]
  }
]
// Specify additional Adtech URLs to register this trigger with.
Attribution-Reporting-Redirects: <Ad tech partner URIs, comma-separated>

Here's an example with sample values added:

Attribution-Reporting-Register-Event-Trigger:
[{
    "trigger_data": "1122", // Returns 010 for CTCs and 0 for VTCs in reports.
    "trigger_priority": "3",
    "deduplication_key": "3344"
}]
Attribution-Reporting-Register-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
  // will take up 9 bits in the resulting key.
    "key_piece": "0x400",// Conversion type purchase = 2
    // Apply this key piece to:
    "source_keys": ["campaignCounts"]
  },
  {
  // 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 will take up 7 bits of space in the resulting key.
  "key_piece": "0xA80",
  // Apply this key piece to:
  "source_keys": ["geoValue", "nonMatchingIdsListedHereAreIgnored"]
  }
]
Attribution-Reporting-Register-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
  }
]
Attribution-Reporting-Redirects:https://adtechpartner.example?app_install=567

If Attribution-Reporting-Redirects contains URIs of ad tech partners, the Attribution Reporting API then makes a similar request to each URI. Each ad tech partner must configure a server that responds with these headers:

// Metadata associated with trigger.
Attribution-Reporting-Register-Event-Trigger: 
[{
    // 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]",
    "trigger_priority": "[signed 64-bit integer]",
    "deduplication_key": "[signed 64-bit integer]"
}]

// Specify a list of dictionaries that generates aggregation keys.
Attribution-Reporting-Register-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"]] 
  },
  ..
]
// 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. 
Attribution-Reporting-Register-Aggregatable-Values: 
[
   // Each source event can contribute a maximum of L1 = 2^16 to the aggregate
   // histogram.
  {
   "[key_name]": [value]
  }
]
// The Attribution-Reporting-Redirects header is ignored for ad tech partners.

Accept event-level reports

This endpoint should be addressable from a URI. Until Privacy Sandbox on Android provides support for enrolling your own postback URI, the URI is inferred from the eTLD+1 of servers used for accepting source registration and trigger registration. Using the example URIs for endpoints that accept source registration and accept trigger registration, this endpoint’s URI is:

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

Configure this server to accept JSON requests that use the following format:

{
  "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", //example value.
}

Accept aggregatable reports

This endpoint should be addressable from a URI. Until Privacy Sandbox on Android provides support for enrolling your own postback URI, the URI is inferred from the eTLD+1 of servers used for accepting source registration and trigger registration. Using the example URIs for endpoints that accept source registration and accept trigger registration, this endpoint’s URI is:

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

Configure this server to accept JSON requests that use the following format:

{
  "source_site": "android-app://com.advertiser.example.publisher",
  "attribution_destination": "android-app://com.advertiser.example.advertiser",
  "source_registration_time": "[timestamp in seconds]",

  // Info that the aggregation services also need encoded in JSON
  // for use with AEAD. Line breaks added for readability.
  "shared_info": "{\"scheduled_report_time\":\"[timestamp in seconds]\",
                   \"privacy_budget_key\":\"[string]\",
                   \"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]"
}

Set up Android client

The client app registers attribution sources and triggers, and enables event-level and aggregatable report generation. To prepare an Android client device or emulator for using the Attribution Reporting API, do the following:

  1. Set up your development environment for the Privacy Sandbox on Android.
  2. Install a system image onto a supported device or set up an emulator that includes support for the Privacy Sandbox on Android.

Register ad events

Your app will need to register sources and conversions as they occur to ensure that they are properly reported. The MeasurementManager class features methods to help you register attribution source events and conversion triggers.

Register an attribution source event

When an ad is viewed or clicked, a publisher app calls registerSource() to register an attribution source as shown in the code snippet.

The Attribution Reporting API supports the following types of attribution source events:

  • Clicks, which you typically register within a callback method similar to onClick(). The corresponding trigger event usually occurs soon after the click event. This type of event provides more information about user interaction and is therefore a good type of attribution source to give a high priority.
  • Views, which you typically register within a callback method similar to onAdShown(). The corresponding trigger event might occur hours or days after the view event.

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

After registration, the API issues an HTTP POST request to the service endpoint at the address specified by attributionSourceUri. The endpoint’s response includes values for destination, source_event_id, expiry, and source_priority.

If the originating ad tech wishes to share source registrations, the original attribution source URI can include redirects to other ad tech endpoints. Limits and rules applicable to the redirects are detailed in the technical proposal.

Register a conversion trigger event

To register a conversion trigger event, call registerTrigger() in your 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)

After registration, the API issues an HTTP POST request to the service endpoint at the address specified by attributionTriggerUri. The endpoint’s response includes values for event and aggregate reports.

If the originating ad tech platform allows trigger registrations to be shared, the URI can include redirects to URIs that belong to other ad tech platforms. The limits and rules applicable to the redirects are detailed in the technical proposal.

Generate and deliver reports

The Attribution Reporting API sends reports to the endpoints on your server that accept event-level reports and aggregatable reports.

Force reporting jobs to run

After you register an attribution source event or register a trigger event, the system schedules the reporting job to run. By default, this job runs every 4 hours. For testing purposes, you can force the reporting jobs to run or shorten the intervals between jobs.

Force attribution job to run:

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

Force event-level reporting job to run:

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

Force aggregatable reporting job to run:

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

Check the output in logcat to see when the jobs have run. It should look something like the following:

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

Force delivery of reports

Even if the reporting job is forced to run, the system still sends out reports according to their scheduled delivery times, which range from a couple of hours to several days. For testing purposes, you can advance the device system time to be after the scheduled delays to initiate the report delivery.

Verify reports on your server

Once reports are sent, verify delivery by checking received reports, applicable server logs such as the mock server history or your custom system.

Testing

To help you get started with the Attribution Reporting API, you can use the MeasurementSampleApp project on GitHub. This sample app demonstrates attribution source registration and trigger registration.

For server endpoints, consider the following reference resources or your custom solution:

  • MeasurementAdTechServerSpec includes OpenAPI service definitions, which can be deployed to a supported mock or microservices platforms.
  • MeasurementAdTechServer includes a reference implementation of a mock server based on Spring Boot app for Google App Engine.

Prerequisites

Deploy mock APIs on remote endpoints accessible from your test device or emulator. For ease of testing, refer to the MeasurementAdTechServerSpec and MeasurementAdTechServer sample projects.

Functionality to test

  • Exercise attribution source and conversion trigger registrations. Check that server-side endpoints respond with the correct format.
  • Execute reporting jobs.
  • Verify delivery of reports on your test server’s backend or console.

Limitations

For a list of in-progress capabilities for the SDK Runtime, view the release notes.

Report bugs and issues

Your feedback is a crucial part of the Privacy Sandbox on Android! Let us know of any issues you find or ideas for improving Privacy Sandbox on Android.