Streaming is available in most browsers,
and in the Developer app.
-
Meet AdAttributionKit
Introducing AdAttributionKit, a new iOS framework for privacy-preserving ad attribution. Learn how AdAttributionKit supports re-engagement, click-through attribution (including support for custom creative), JWS formatted impressions and postbacks, and more. We'll provide insight into testing your AdAttributionKit implementation and deep-dive into code snippets and best practices for adopting AdAttributionKit.
Chapters
- 0:00 - Introduction
- 2:05 - Fetching ads
- 3:55 - Displaying ads
- 8:18 - Postbacks
- 16:28 - Re-engagement
- 21:09 - Testing AdAttributionKit
Resources
- AdAttributionKit
- Attributing ads with SKAdNetwork and Private Click Measurement
- Forum: App & System Services
Related Videos
WWDC22
-
Download
Hello everybody, I’m Nikhil from App Store Engineering. Today, we meet AdAttributionKit. Privacy is a big part of everything we do at Apple. Privacy preserving ad attribution enables advertising attribution while preserving user privacy.
Privacy preserving ad attribution is built on top of crowd anonymity. At a high level, crowd anonymity will send less data back to ad networks and advertisers, when there are fewer conversions.
As the crowd size scales up, more data is sent back.
Once the crowd size passes the highest level, the maximum amount of data is returned to ad networks and advertisers.
SKAdNetwork was Apple’s first privacy- preserving ad attribution framework powered by crowd anonymity. Today I am happy to introduce AdAttributionKit, our new ad attribution framework for iOS and iPadOS built on top of SKAdNetwork fundamentals. Let’s begin by talking about ad attribution at a high level. Ad networks generate, sign and serve ads to publisher apps.
Publisher apps display the ad to the person using them.
They install the advertised app, and as they engage with it over time, and after the measurement period has passed, a postback containing conversion information is sent to the ad network.
With that flow in mind, here are the topics I will be covering today. We will start with a discussion on fetching ads and the new ad impression format in AdAttributionKit. Then we will look at displaying ads. We’ll follow this with a discussion about postbacks. Then we’ll go into re-engagement and briefly cover testing AdAttributionKit.
Let’s talk about fetching ads. Ads are the input to the attribution flow. The publisher app will fetch the ad data from the ad network. This ad data is in compact JWS format. I will now go over the fields. Here is an ad impression in JSON form.
I will cover a few important fields.
The "advertised-item-identifier" is the app ID of the app being advertised.
The "publisher-item-identifier" is the app ID of the app showing the ad.
The "source-identifier" is a 4 digit integer used to represent campaign-related information for the ad. This field retains the same behavior from SKAdNetwork 4.0.
The fields in the JSON are described in the AdAttributionKit documentation linked to the session.
With that structure in mind let’s move on to ad impressions. AppImpression instances represent ad impressions. These instances are initialized with the ad’s compact JWS representation, which we covered in the previous section.
I will now show you how to create an app impression from the compact JWS.
First, I fetch the ad’s compact JWS representation. Note that the fetchAdImpressionJWS() method is not shown implemented since this is entirely up to how I integrate with my ad provider or ad network.
I then pass this jwsString to the initializer for AppImpression. I will be using this AppImpression instance in the next section to discuss displaying ads. AdAttributionKit supports 3 different methods of displaying ads. Let’s take a look at the ads I want to display for my advertised app.
The first ad is a custom creative that is clickable. Tapping a custom click ad will navigate the user to a marketplace where they can install the advertised app.
Here is a view-through ad in a different screen. An example of this type of ad content can be a video ad.
Another ad type simply recommends the advertised app. For this I can use SKOverlay which shows up as a banner within the context of my app.
Or I can use SKStoreProductViewController which has more of a full-screen presentation model still within the context of the app. Both SKOverlay and SKStoreProductViewController share the iOS design language and are extremely easy to adopt and plug in to my app.
Here is how to implement a custom click ad.
First, I create the customAdView.
Then I create an instance of UIEventAttributionView and add it as a subview of my ad view.
Note the UIEventAttributionView has to be on top of any view that receives user interactions.
With the ad view set up, let’s handle taps on it. When the user taps on the ad view, I grab my instance of appImpression and call the handleTap() method on it.
Note that handleTap has to be called within 15 minutes of when the appImpression instance is initialized. Past 15 minutes, create a new appImpression instance.
When the click happens and you call the handleTap method, AdAttributionKit navigates the user to a marketplace from where the advertised app can be installed.
For view-through ads, I previously mentioned the usage of a video ad. However view-through ads can cover any custom ad presentation.
I’ll use a simple timeline to help visualize the events and API calls involved.
When the ad presentation starts, I call the beginView() API on my appImpression instance, and when the ad presentation finishes, I call the endView() API on my appImpression instance.
In my specific case of the video view-through ad, when the video starts playing, I call beginView() on my appImpression instance, and when the video finishes, I call endView() on my appImpression instance. A few things to note about view-through ads. Your custom ad content needs to be displayed for a minimum of 2 seconds - meaning, the time between calling beginView() and endView() should be at least 2 seconds. This matches the industry standard for this type of ad content.
beginView and endView need to be called on the same instance of appImpression, and the same ad network cannot have multiple open view-through ad presentations for the same advertised app at the same time. Every call to begin must be balanced by a call to end.
Moving on to SKOverlay.
Implementing SKOverlay follows the same pattern in AdAttributionKit as it did in SKAdNetwork. We use instances of AppConfiguration to set the SKOverlay up.
You will set your instance of appImpression to the new appImpression property on AppConfiguration.
Finally, SKStoreProductViewController. The setup for SKStoreProductViewController is similar to how it was setup in SKAdNetwork.
The loadProduct method is called to prepare the product page UI for display.
The main difference is the impression parameter on the loadProduct method to which you will pass your appImpression instance.
And now, postbacks.
Postbacks are the outputs of the attribution system. They are the conversion signals sent to the ad network. They are also optionally sent to the developer of the advertised app.
AdAttributionKit sends postbacks as a combination of a signed compact JWS and a few unsigned fields. Here is the signed portion.
Let’s focus on a few important fields.
The "conversion-type" field carries information about the type of conversion that created this postback. The values for this field can be download, redownload, or re-engagement.
The "marketplace-identifier" field carries the identifier of the marketplace where the conversion occurred. In our examples, because the download is taking place on the Apple App Store, this field has the value "com.apple.AppStore".
The "publisher-item-identifier" is the identifier of the app that showed the original ad. For low levels of crowd anonymity, this will not be included in the postback.
The "source-identifier" has a minimum of 2 digits and a maximum of 4. Crowd anonymity determines how many digits are included in the postback.
Let’s now take a look at the rest of the postback request body.
The "jws-string" key holds the JWS compact representation of the JSON we just covered. Note that I am showing a placeholder here and not the actual string for the sake of brevity.
The "ad-interaction-type" key holds information about how the user interacted with the ad. The possible values for this are "click" and "view". I will go into detail about this key in a subsequent section.
The "conversion-value" key holds the value of the conversion as determined by the advertised app making API calls to AdAttributionKit.
Note that "conversion-value" is also a crowd anonymity controlled field, and as such, it is not guaranteed to be present in the postback.
If crowd anonymity determines the data allowance to not be high, instead of the "conversion-value", you might receive the "coarse-conversion-value".
or you might receive no conversion value at all.
Going back to the full postback request body, I want to dive deeper on two topics. The "conversion-value" and the "ad-interaction-type".
First, conversion values. Conversion values can be used to measure advertising effectiveness. In other words, how valuable was this ad campaign to my advertising needs? The conversion value can also be used to derive information about the return on ad spend or ROAS. This is quite an important heuristic for determining future ad spend and related optimization. Note that conversion values can be fine or coarse. This is determined by crowd anonymity. I will go over 3 scenarios where I want to measure user engagement and update the "conversion-value" via AdAttributionKit.
Let’s look at an account creation scenario first. Assume that we are inside the method handling account creation in the app.
I will first create a PostbackUpdate struct, and then I call the updateConversionValue method on the Postback type to execute the update.
Let’s take a closer look at the PostbackUpdate struct’s properties.
The first argument is the fineConversionValue. This is a positive integer between 0 and 63 both values included. Note that the values for the fineConversionValue need to make semantic sense for my advertising campaign and how its effectiveness is being measured. Typically, the schema for conversion values would be determined after discussion and planning between me as the advertised app developer, my chosen ad network and any MMPs I might choose to work with.
The second argument is for locking postbacks. The lock is a way for me to tell AdAttributionKit that I am finished measuring and recording conversion values for that particular conversion window. This allows AdAttributionKit to freeze that postback and immediately schedule it for transmission.
Another way to think about this is that, a very high value event happened that I want to know about as soon as possible. The time urgency of this event beats out measuring anything else in that conversion window. Hence, I call lock, and AdAttributionKit knows to freeze and schedule this postback.
Account creation does not have enough value for me to lock the postback, therefore I use false for the lock argument.
The final argument is the coarse conversion value, which can be one of three string values: low, medium, or high. As mentioned before, the coarse value is sent in the postback when crowd anonymity determines that the fine value cannot be sent.
With the account created, the user watches their first video. I consider this a higher-value event and so I update the fineConversionValue to 38.
The value of the user watching a video is not high enough for me to lock the postback and so that argument is false.
And if my fine value cannot make it through due to crowd anonymity, I set my coarseConversionValue to be medium as a fallback measurement.
Finally, the user uploads a video. I consider this a very high-value engagement and so I update my fine value to be 42.
Video creation is a very high-value event, and I want to receive my postback as soon as possible. Therefore, I lock the postback via the lockPostback argument in the API.
Note that locking the postback does not lead to its instant transmission. This delayed transmission behavior is to further protect user privacy.
Again as a fallback, I set my coarseConversionValue to be high.
You can use the PostbackUpdate structure to set your conversion value for different scenarios in your app.
The last thing to cover for postbacks is the ad interaction type. AdAttributionKit distinguishes between the user tapping an ad and simply viewing it. This information is crucial for the selection of the ad that drove the conversion.
The ad interaction type in the postback will have the value "click" or "view" depending on the user’s interaction with the ad. Let’s review the value of this field for different presentation methods.
For a custom click ad, viewing it does not create an impression.
Tapping it results in an ad interaction type of "click".
For a view-through ad, viewing it results in an ad interaction type of "view". Tapping it does not create an impression.
For SKOverlay, presenting it results in an ad interaction type of "view". Tapping it results in "click".
Similarly for SKStoreProductViewController, presenting it leads to ad interaction type "view", tapping it leads to "click".
Conversion values and ad interaction type provide details about the user engagement with your app and the ad that drove the install. And now, for something even newer - re-engagement. The main purpose of ad attribution is to attribute a conversion event to an impression event. In our examples thus far I used installation as the conversion event. The initial goal for many ad campaigns is to drive installations for the app. This makes sense since installation is the first step in the user’s journey with the app. Now, let’s talk about a new type of conversion event - re-engagement For re-engagement ad campaigns, the goal is to bring users back to the advertised app.
Consider the case where the person who installed the advertised app stopped using it. The advertised app wants to recover this user.
And so they run an ad showing a limited-time offer. When the user taps on the ad, AdAttributionKit opens the advertised app to the screen with the offer, and the re-engagement begins.
Let’s take a look at a re-engagement ad impression. For opting into re-engagement, I will add one more parameter to the payload: "eligible-for-re-engagement".
This tells AdAttributionKit that the ad impression is to be considered for re-engagement conversions.
An ad impression configured with the "eligible-for-re-engagement" property, is considered for re-engagement or install depending on whether the advertised app is installed or not.
For a normal ad impression without re-engagement opt-in, it would only be considered for installs.
Here is how to initiate a re-engagement in code.
I create the AppImpression instance with the jwsString. Note that this is the JWS impression that has opted in for re-engagement.
Then, in the tap handler for my ad view, first I get the universal link that I want the user to be navigated to within the advertised app.
Then, I call the handleTap() method on the appImpression instance passing in the reengagementURL. This call tells AdAttributionKit to open the advertised app.
When the advertised app is opened, the re-engagement URL is passed to it. Note the query parameter appended to the re-engagement URL. AdAttributionKit appends this parameter to the URL during a re-engagement.
Advertised apps can check for this parameter to tell if an open was caused by an AdAttributionKit re-engagement.
SKOverlay and SKStoreProductViewController are also compatible with re-engagement.
The setup for both is very similar to an install.
To open a universal link in the advertised app, you can use the adAttributionReengagementURL property on the SKOverlay.
And for SKStoreProductViewController, you can use the reengagementURL parameter on the loadProduct method. Let’s talk about the postbacks sent for re-engagement conversions.
The conversion type key in the postback will have the value of re-engagement.
Re-engagement conversions can only have the click ad interaction type. View-through re-engagement is not supported by AdAttributionKit.
The first call to update the conversion value must be made within 48 hours of the conversion.
Re-engagement postbacks can be selectively updated, separate from installation postbacks. I will show you how.
Let’s bring back the PostbackUpdate struct we saw earlier.
Use the conversionTypes parameter to control which postbacks get updated.
For updating only the re-engagement postbacks, I use the .reengagement value for conversionTypes.
For updating only the install postbacks, I use the .install value for conversionTypes.
For updating all postbacks, I use both .install and .reengagement for conversionTypes. AdAttributionKit also updates all postbacks if the conversionTypes parameter is nil.
That covers re-engagement, which in addition to install presents a complete picture of how people are engaging with apps. Testing AdAttributionKit can be difficult due to the asynchronous design by virtue of necessity and purpose.
This asynchronous nature is further compounded by time randomization performed in order to strengthen our privacy behavior. To improve the ease of testability, we have a new developer mode for AdAttributionKit.
Developer mode removes time randomization from the system to make it time-deterministic. It shortens the conversion windows, so testing of conversion value updates can be done in all three windows in a test-friendly timeframe.
Developer mode also speeds up postback transmission to help with testing. Let’s look at how to turn on developer mode.
In iOS Settings, navigate to the Developer menu and scroll down to the switch for AdAttributionKit Developer Mode.
When the switch is turned on, AdAttributionkit begins to function in developer mode. The time durations in the system are shortened to enable faster testing.
And that is all I have for you today on AdAttributionKit, Apple’s new privacy-preserving ad attribution framework for iOS and iPadOS.
As an ad network, if you are already registered for use with SKAdNetwork, no further enrollment is needed. If not, you start by enrolling your ad network with AdAttributionKit.
After enrollment, the next steps would be to serve the new JWS format ads and display them using AdAttributionKit.
For advertised apps, adopt the new AdAttributionKit APIs for updating conversion values.
On the server side, receive the new JWS format postbacks and parse them for analysis.
For all the features discussed here today, note that AdAttributionKit is fully interoperable with SKAdNetwork. And for more information about SKAdNetwork, check out the WWDC22 session "What’s new with SKAdNetwork".
And finally, our AdAttributionKit documentation is a fantastic resource as you get started with your AdAttributionKit integration. Thank you so much for joining me today!
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.