Incipia blog

SKAdNetwork & AppTrackingTransparency in iOS 14

Gregory Klein | July 19, 2023

For the mobile app advertising industry, one of the most significant updates from WWDC this year regarding iOS 14 was the emphasis of SKAdNetwork and AppTrackingTransparency, and—as they allude to—the upcoming constraints of the IDFA and tracking ad entities.

 

If you are looking for guidance on how to establish and execute on an iOS 14 plan, contact us or send an email to gabe@incipia.co to inquire about our iOS 14 consulting services.

 

SKAdNetwork

The purpose of SKAdNetwork, according to Apple, is for "helping advertisers measure the success of ad campaigns while maintaining user privacy". The framework was introduced along with iOS 11.3 in 2018, and with iOS 14, SKAdNetwork is being brought to a second version that incorporates the ability to track conversion events. Given the nature of this framework being entirely related to advertising, there are three main components involved:

  1. Ad networks: sign ads and receive install validation postbacks when ads produce conversions
  2. Source apps: display ads provided by the ad networks
  3. Advertised apps: appear in signed ads

Ad Networks

Ad networks must register themselves with Apple in order to receive install validation postbacks via the SKAdNetwork API at whatever url they specified during their registration.

Registering an ad network mostly involves visiting the Ad Network ID Request Form. From there, they receive an ad network ID upon providing a PKCS#8 public key used for signature validation; ad network IDs will take the following form: "example123.adnetwork", and they are supposed to be made publicly available so developers can use them to display ads from that network.

Source Apps

If a developer wants to display ads from an ad network, the first step they must take is to configure their app by adding the ad network ID to its Info.plist file.

Displaying an advertised product/initiating a validation involves calling the loadProduct(withParamters:completionBlock:) method, which requires a signature key that the ad network generates, known as the SKStoreProductParameterAdNetworkAttributionSignature.

Ad networks use install validation keys when calling the product view controller's method for loading a product; the keys describe an impression of an ad campaign and the information they contain associates an app installation with an ad campaign. At the time of this post, it is still unclear as to whether SKAdNetwork will support the ability to track lower-level granularities, like ad sets or creatives.

Advertised Apps

The two main responsibilities of the advertised app are to call the registerAppForAdNetworkAttribution() method when the app first launches, as well as optionally calling the updateConversionValue(_:) method potentially more than once during a period of time according to specific timer logic later described in this post; each are class methods provided by the StoreKit framework, and they are both capable of generating an install notification. However, the registerAppForAdNetworkAttribution() method postback does not contain any conversion-related data.

Sequence of Events

Based on Apple's documentation, the following diagram shows the sequence of events where a hypothetical app, "App A", displays an ad for "App B", and a user produces a conversion:

Conversion Tracking

As reflected in the Sequence of Events diagram, there are two ways to send conversion postbacks via the SKAdNetwork API:

  • registerAppForAdNetworkAttribution()
  • updateConversionValue(_:)

Calling the registerAppForAdNetworkAttribution() method is meant to happen right when the app first launches in order to generate an install notification that reflects the app being installed as a result of an ad; calling it again after the first time will have no effect. The timer logic is as follows:

Postback Timer Logic

  1. After registerAppForAdNetworkAttribution() is first called, a 24-hour timer starts
  2. Within the 24-hour period, updateConversionValue(_:) can be called using a 6-bit integer according the user's behavior
  3. If the 6-bit value passed into updateConversionValue(_:) is greater than the previous one used, the 24-hour timer will restart
  4. Once 24 hours passes without updateConversionValue(_:) being called, or being called but only with 6-bit values lower than what was previously used, the postback notification is sent within a random timeframe of 0–24 hours

One straightforward context to understand the timer logic in tandem with updateConversionValue(_:) would be a simple game that follows a linear progression of levels. Let's say each level has an associated conversion value of the level number itself (e.g. Level #1 has a value of 1, Level #2 has a value of 2):

  1. User installed the app, registerAppForAdNetworkAttribution() is called, and the 24-hour timer starts; the conversion value is absent
  2. If the user beats the first level within 24 hours, updateConversionValue(_:) is called with a value of 1, and the 24-hour timer restarts
  3. The 24-hour timer will continue to restart so long as the user continues to complete each level within 24 hours of finishing the previous one—for every level completed, updateConversionValue(_:) will be called with a value that corresponds to the last level completed
  4. This will keep happening until the user does not complete a level within 24 hours of starting it, or if the user reaches level 65, in which case updateConversionValue(_:) will have been called with the highest possible value of 64
    1. Note: once either of these things happen, the second timer will be triggered with a random value between 0 and 24 hours, and only until the second time expires will the postback be sent

Postback Request

The postback received by the ad network will take the following JSON form:

{
  "version": "2.0",
  "ad-network-id": "com.example",
  "campaign-id": 42,
  "transaction-id": "6aafb7a5-0170-41b5-bbe4-fe71dedf1e28",
  "app-id": 525463029,
  "attribution-signature": "MDYCGQCsQ4y8d4BlYU9b8Qb9BPWPi+ixk\\/OiRysCGQDZZ8fpJnuqs9my8iSQVbJO\\/oU1AXUROYU="
  "redownload": 1,
  "source-app-id": 1234567891
  "conversion-value": 20
}

Tracking Conversion Events

As stated above, the conversion-value field must be given an unsigned 6-bit value, and it is the ad network's responsibility to determine the meaning of the value. Based on the timer logic—and even more based on the fact that the highest 6-bit value is 64—this field is not at all meant to capture an aggregation of events; it is meant to capture the single highest value event a user performs within the rolling 24-hour period since installing and launching the app.

Tracking Campaigns

According to Apple, the campaign-id field is associated with the SKStoreProductParameterAdNetworkCampaignIdentier key that ad networks use to determine their own campaign identifiers, "which must be an integer between 1 and 100". This doesn't only mean that there will now be an added responsibility for some party to map the channel-assigned entity IDs to a value from 1 to 100, but it also means that only 100 active entities will be trackable, per ad network, at any given time.

AppTrackingTransparency

Before iOS 14, the IDFA could only be turned off by visiting the Settings app and going to Privacy > Advertising to enable the "Limit Ad Tracking" (LAT) switch. Note the auxiliary label that appears underneath it:

Opt out of receiving ads targeted to your interests. You may still receive the same number of ads, but the ads may be less relevant to you.

The discoverability of the LAT setting has been low and the unbiased message provided by Apple may even be seen as guiding the user to keep it turned on. For those two reasons (mostly the first), ~15.6% of users had LAT turned on globally this year.

Also, LAT before iOS 14 was a setting that applied across all apps; with iOS 14, this setting will not only sit at the app-level, but a new dialog must now be presented to users if developers want to access it—this can be done via the new AppTrackingTransparency framework by calling the requestTrackingAuthorization(completionHandler:) method:

 

In order for developers to access the user's IDFA, the user must select the "Allow Tracking" option when this dialog is presented; the dialog is only capable of being presented one time per app install, so if they select "Ask App Not to Track", then there is no opportunity to present this again in the future for that app.

The only customizable part of the dialog shown above is the secondary label that reads: "Your data will be used to deliver personalized ads to you." Developers can configure the NSUserTrackingUsageDescription to replace the default text with a message that informs the user why an app is requesting permission to use data for tracking the user or the device.

Closing Remarks

One of the most interesting ideas I've come across while researching the anticipated effects of iOS 14 is the usage of a bitmask to track as much information as possible with the unsigned 6-bit integer conversion value sent via the SKAdNetwork API; this was talked about in a video posted by Shamanth Rao, the CEO at RocketShip HQ.

A rough outline of the idea for a simple funnel-based optimization is as follows:

  1. Assign 3 key events to three bits, which will be applied to a "logical OR" to combine together:
    1. 001: registration
    2. 010: subscription
    3. 100: share
    4. 011: register & subscribed
  2. Assign 3 bits (8 days) to encode the days since the install

I don't doubt that some, if not many, app publishers will actually take an approach like this in trying to maintain insight into user activity and I think this is a creative use of the 6-bit conversion value in an install postback; but I ultimately think the direction this points in tells a much bigger and unfortunate part of the story, which is that the future of attempting to attribute users and their activity to campaigns is going to become way more complex, while also much more limited.

Using the bitmask approach to support my point in how complex this can become, the assertion that each time updateConversionValue(_:) is called must be done with a higher value than the last would add a completely new dimension to the logic (e.g. "011" will translate to lower value than "100", so the leading bits must account for producing an overall higher number to reflect combined events, and that would get in the way of using them to capture days since install).

Eric Seufert wrote a great blog post that spells out improvements of which could potentially be made by Apple. The three biggest immediate considerations are:

  1. The ability to track the magnitude and frequency of conversion events
  2. Increasing the limit from being able to track 100 campaigns to a larger and more useful amount
  3. Giving advertisers the ability to retrieve their apps attribution data directly from the SKAdNetwork API, as opposed to granting ad networks first-party ownership of that data

Along with observing what Apple does over the coming months in terms of adjustments to the API (which there's a good chance that they won't do anything), many people are also anticipating apps to evolve as well—for instance, they are likely to adopt UX patterns that will be geared towards getting users to engage in a high-value event in the shortest timeframe.