Intelligent tracking prevention

This is for:

Developer

In this article, we’ll discuss the implications of Intelligent Tracking Prevention (ITP) and our API solution that will allow you to mitigate the impact of ITP.

Intro

Intelligent Tracking Prevention (ITP) is a feature developed by Apple to improve privacy for users of the Safari web browser, as part of the Webkit framework, which accounts for most iOS traffic and a small proportion of desktop traffic.

Tip
Leading practice

It is worth pointing out that ITP is directed at the use of cross-site tracking rather than at Qubit’s technology, which is designed solely to enable brands to understand and action the quantitative and qualitative website data they collect and use to deliver meaningful and impactful personalizations.

Iterations

ITP 1.0 and 1.1

Most of the efforts around ITP, bundled into ITP versions 1.0 and 1,1, have been to prevent cross-site tracking through the use of 3rd-party cookies, which has now been rendered impossible in Safari.

Versions 1.0 and 1.1 of ITP allowed first-party trackers to behave like third-party trackers, as long as the user visited the site within 24 hours of clicking affiliate link (ad). After this period, that cookie could no longer be accessed to track the user’s behavior. First-party cookies remained on the user’s browser, but Safari would delete them entirely after 29 days.

Warning

Qubit technology does not use 3rd-party cookies, so we were not affected by ITP 1.0 and 1.1.

ITP 2.1

ITP version 2.1 introduced a cookie rule limiting the storage of client-side, first-party cookies–persistent cookies created through document.cookie–to one week, after which, they are removed from the browser.

Warning

Any experiences that set/get cookies using document.cookie directly, or through an API like cookieman will be subject to ITP restrictions; any data persisted will only last for a week. See Our solution for details of our response and how you can mitigate the impact of ITP 2.1.

ITP 2.2

ITP version 2.2 reduced the storage of client-side, first-party cookies further, to one day, after which, they are removed from the browser. This update only applies to cookies that use a method called link decoration to drop the first-party cookie and used as a means of delivering cross-site tracking.

With this version, when a webpage is navigated to from a domain classified by ITP and the landing URL has a query string or fragment, the expiry of persistent client-side cookies created on that page is 24 hours.

Warning

Qubit does not use link decoration and therefore our technology solution is not affected. There is no need for our clients to take further action.

Note

Where link decoration is used, ITP 2.2 prevents cookies from being stored for more than one day for visitors on iOS and Desktop Safari. Core Qubit cookies will remain unaffected. To persist data long term, Custom experiences will need to use our cookie API described below.

However, there is an important side effect of this update that our clients should be aware of. If a person is directed to your site from a third-party affiliate site that uses link decoration on the URL, Safari will cap all cookies that get set during that page load, including Qubit’s cookies, which are written on every single page view and often more frequently. In this scenario, Safari will apply the same one-day restrictions to Qubit’s first-party cookies, and as a result, Qubit cookies will not be set using a cookie only method.

Warning

Where this scenario is in play, clients will need to implement our solution based on localStorage and ETags, as outlined below, to ensure the data collection required to power Qubit’s personalizations is not affected.

ITP 2.3

ITP version 2.3, an evolution of version 2.2, and the most recent update, released in September 2019, closes loopholes in the use of link decoration and localStorage to bypass ITP restrictions and specifically targets script-writeable data.

Qubit’s clients will only be affected by this update if a person is directed to your site from a third-party affiliate–and of course, only if the affiliate domain is classified as having cross-site tracking capabilities that utilizes link decoration. For example, if a customer arrives via a voucher code website, which uses a URL parameter extension.

In this scenario, Safari will mark all script-writeable data for deletion in seven days, including Qubit scripts. In summary, this update caps the lifetime of all script-writeable website data after a navigation with link decoration from a classified domain.

Warning

Where this scenario is in play, clients will need to implement our solution based on localStorage and ETags as outlined below to ensure the data collection required to power Qubit’s personalizations is not affected.

Further updates

We are aware of recent announcements made by both Google and Apple regarding the blocking of third-party cookies in the browser and changes to cookie/localStorage expiry. We would like to take this opportunity to reiterate that Qubit does not set third-party cookies and that we have engineered a solution to address cookie/localStorage expiry.

Qubit customers will not be affected by the changes announced by Google and Apple.

It is clear that this is a fast-moving and fluid situation and that both Apple and Google will continue to iterate as they look to design solutions to enhance privacy. Qubit will continue to monitor the impact of ITP and wider industry plans.

Our solution

Tip
Leading practice

For more information about our solution and how to enable it for your property, reach out to your CSM.

localStorage and entity tags

Our core cookies, _qubitTracker, qb_permanent, qb_session, used to store experience and segmentation data, and otherwise:

  • Identify individual customers, and tie them into the data you send us

  • Target customers with a consistently personalized journey

are backed up using a combination of localStorage and Entity Tags (ETags)–a way to use the browser cache to persist data.

Qubit’s Custom experiences interact with cookies using custom JavaScript, so there is no easy way to automate a cookie back-up process. For this reason, any experiences that set/get cookies using document.cookie directly, or through an API like cookieman will be subject to ITP restrictions; any data persisted will only last for a week. This is particularly problematic for experiences, such as welcome modals, that ideally should be shown only once to users.

To mitigate the impact of ITP, we have built an API, that is provided to experiences through options. This writes to a single cookie, under the key qb_generic. Every time this cookie is written to, the API will back up the cookie in localStorage and ETags. When no cookie is present, because it has expired or been deleted, the cookie is seamlessly restored from the back-up.

Sharing localStorage data

As a method of mitigating ITP restrictions, localStorage works well for syncing cookies. However, there is no way for websites to share localStorage data between pages served on different subdomains, even if the top-level domain is the same. The same is true for sharing localStorage data between HTTP and HTTPS pages, again, even if on the same domain.

As a workaround, cookie data can be synced using the browser network cache, which can be shared between pages on different protocols and subdomains and is not subject to the one-day restriction under ITP2.2.

How is data stored in the browser network cache?

Storing data in the network cache can be done via a GET request using a custom header called Set-State–a POST request would disable caching.

The server, https://api.qubit.com/stamp replies with a response body identical to the Set-State header it was sent, along with an ETag; this ensures the browser stores the response indefinitely.

In this process, the server doesn’t actually save any state, it merely instructs the browser to keep state in the network cache.

When the browser does a GET request to the server without the Set-State header, the server responds with a 304 not modified status, so the browser will retrieve the state it cached.

Further updates can be applied to the state using a Set-State header, where the server would return with a new ETag and a 200 status, causing the browser to update the state stored in the network cache.

Limitations

Through testing, we have verified this way of storing state works well across subdomains and across protocols and will persist indefinitely. In Google Chrome, data can even be shared between top-level domains.

However, it is worth bearing in mind that our implementation intentionally prevents that by including your tracking Id in the request.

How does the syncing logic work?

Any cookie updates are persisted to:

  • Browser cookie

  • Local Storage

  • Network cache

Updates are debounced using a 5-second window to reduce requests to the server. When new sessions are detected in a browser, each of these stores is checked. The store most recently updated will be chosen as the source of truth and written into the cookie.

Generally, you should expect to see around 3 HTTP requests per page view, though these requests are almost always sent after the page is loaded.

Performance impact

During our testing, which included working directly with customers to conduct an A/B test to measure effectiveness, there was no detected impact on page load speed.

cookies.set

Sets a cookie value. All values are cast to a string, so when reading, they’ll be returned as strings. The API will automatically escape/unescape values, but objects need to be JSON stringified, though storing JSON in cookies is considered wasteful and not advised:

// or execution
function activation (options, cb) {
  options.cookies.set('modalShown', 1)
  options.cookies.set('data', JSON.stringify({ foo: 'bar' }))
}

By default, these values will never expire but there is also an option that can be used to expire the persisted value at a certain date:

// or execution
function activation (options, cb) {
  options.cookies.set('modalShown', '1', {
    // 1 week, supports Dates and Timestamps
    expires: Date.now() + 1000 * 60 * 60 * 24 * 7
  })
}

cookies.get

Gets a cookie, or returns undefined if it doesn’t exist:

function activation (options, cb) {
  // returns '1' after previous example
  options.cookies.get('modalShown')

  const data = JSON.parse(options.cookies.get('data') || '{}')
}

cookies.clear

Deletes a cookie:

function activation (options, cb) {
  options.cookies.clear('modalShown')
  options.cookies.clear('data')
}

Migrating experiences

We recommend migrating experiences that write and read cookies via document.cookie and/or an API such as cookieman to the options.cookies API, passed via experience options.

Mobile experiences should be prioritized and migrated first, as iOS traffic represents a large proportion of mobile visitor traffic.

To migrate an experience, read using options.cookies.get and use the old method as a fallback, then write using options.cookies.set. This way, any cookies written by the experience before the migration will be preserved.

In the following example, we show an activation reading and writing a welcomeShown cookie using cookieman, to ensure a visitor is only shown a welcome message once:

Before migration:

const cookieman = require('cookieman')

function activation (options, cb) {
  if (!cookieman.val('welcomeShown')) {
    cookieman.set('welcomeShown', 1)
    cb()
  }
}

After migration, preserving previous cookie values:

const cookieman = require('cookieman')

function activation (options, cb) {
  if (!options.cookies.get('welcomeShown') && !cookieman.val('welcomeShown')) {
    options.cookies.set('welcomeShown', 1)
    cb()
  }
}

Draft experiences that have never been published do not require logic to check for the old cookie value and should be written using the options.cookies API for all cookie access.