Safari and iOS ITP 2.1. Its effects on testing and what you can do about them.

Issue Description

As of March 25th, Apple released iOS 12.2 and Safari 12.1 on macOS High Sierra and Mojave. This includes ITP 2.1 (Intelligent Tracking Prevention), which enforces new rules that prevent certain tracking functions in the above-mentioned browsers.

In relation to Convert Experiences, it means that browser-issued cookies now have an enforced duration of seven days. Convert cookies are written exclusively by the browser, so current Convert cookies will be deleted after this seven day period.

In experiment terms, it means that visitors that have been previously bucketed into an experiment will be counted again if they return after seven days. Also, if they convert again after the 7 day period, the new conversion will be counted in the experiment. The default behavior is that Convert counts only the first conversion.

This could affect your experiment results substantially, as the usage of the affected browsers can be large in certain markets like the United States.

Tip
Important

Solution

As mentioned above, the new cookie duration restrictions are on browser-created cookies and not server-issued cookies. Convert's solution is to issue the cookie that tracks experiment and personalization data directly from the customer's web server. In this way, the convert script won't have the restrictions imposed by ITP 2.1 on the browser-side.

To facilitate this, we are making available some code snippets to integrate on our customers' web server infrastructure. These code snippets are available for the following platforms: PHP, Node JS HTTP Module, Node JS Express, Ruby and Python.

PHP Code

<?php

$currentTime = time();
$convertCookieName = "_conv_v";

$yourTopDomain = "example.com";

$defaultCookieData = "vi:1*sc:0*cs:$currentTime*fs:$currentTime*pv:0";

if(isset($_COOKIE[$convertCookieName])) $cookieData = $_COOKIE[$convertCookieName];

else $cookieData = $defaultCookieData;

//set the cookie

setcookie($convertCookieName,$cookieData, $currentTime + 15768000, "/", $yourTopDomain);

?>

Node JS HTTP Module Code

Requires cookie module.

  var currentTime = Math.floor(Date.now()/1000);
    var convertCookieName = "_conv_v";
    var yourTopDomain = "localhost";
    var defaultCookieData = "vi:1*sc:0*cs:"+currentTime+"*fs:"+currentTime+"*pv:0";

    var cookieData = defaultCookieData;
    if(request.headers.cookie) {
      var cookies = cookie.parse(request.headers.cookie);
      if(cookies[convertCookieName]) {
        cookieData = cookies[convertCookieName];
      }
    }

    response.setHeader('Set-Cookie', cookie.serialize(convertCookieName, cookieData, {
      maxAge: 15768000,
      path: '/',
      domain: yourTopDomain
    }));

Node JS Express Code

Requires cookie-parser module required as cookieParser is no longer bundled with Express and must be installed separately.

    var currentTime = Math.floor(Date.now()/1000);
    var convertCookieName = "_conv_v";
    var yourTopDomain = "localhost";
    var defaultCookieData = "vi:1*sc:0*cs:"+currentTime+"*fs:"+currentTime+"*pv:0";

    var cookieData = defaultCookieData;
    if(request.cookies[convertCookieName]) {
      cookieData = request.cookies[convertCookieName];
    }

    response.cookie(convertCookieName, cookieData, { 
      maxAge: 15768000 * 1000,
      path: '/',
      domain: yourTopDomain
    });

Ruby Code

currentTime = Time.now.to_i
convertCookieName = "_conv_v"
yourTopDomain = "example.com"
defaultCookieData = "vi:1*sc:0*cs:#{currentTime}*fs:#{currentTime}*pv:0"

cookieData = defaultCookieData
if request.cookies[convertCookieName]
    cookieData = request.cookies[convertCookieName]
end

response.set_cookie(convertCookieName, 
    :value => cookieData,
    :domain => yourTopDomain,
    :path => "/",
    :expires => Time.at(currentTime + 15768000)
)

Note:
request is instance of Rack::Request
response is instance of Rack::Response

Python Code

import Cookie
import datetime
import os

currentTime = datetime.datetime.now().strftime('%s')
convertCookieName = '_conv_v'
yourTopDomain = 'example.com'
defaultCookieData = 'vi:1*sc:0*cs:'+currentTime+'fs:'+currentTime+'*pv:0'
cookieData = Cookie.SimpleCookie()
if 'HTTP_COOKIE' in os.environ:
    cookieData.load(os.environ["HTTP_COOKIE"])
if (convertCookieName not in cookieData) or (not cooieData[convertCookieName].value.strip()):
    cookieData[convertCookieName] = defaultCookieData
# Set the cookie
cookieData[convertCookieName]['domain'] = yourTopDomain
cookieData[convertCookieName]['path'] = '/'
cookieData[convertCookieName]['max-age'] = 15768000
print cookieData[convertCookieName].output()

 

Shopify (or any similar platform where you are prevented from adding any of the above code)

In the case that you are unable to add the required code (such as on Shopify's platform), the solution will be such as the following:

1) create a subdomain of your main domain and host it somewhere
2) create a script that has the cookie code at the host of the new subdomain
3) load the script mentioned in 2) in Shopify (eg templates, additional scripts, etc.)

 

Contact us through the support channels if you have any questions about the above.

Have more questions? Submit a request

Comments