Example usage

To run an experiment, you'll want to activate the experiment at the point you want to split traffic in your code. The activate function returns which variation the current user is assigned to. The activate function also sends an event to Optimizely to record that the current user has been exposed to the experiment. In this example, we've created an experiment my_experiment with two variations control and treatment.

You'll also want to track events for your key conversion metrics. In this example, there is one conversion metric, my_conversion. The track function can be used to track events across multiple experiments, and will be counted for each experiment only if activate has previously been called for the current user.

Note: The purpose of this example is familiarize you with the track method. However, the example simply shows you how to call the track method right after a bucketing decision without any conditions. Normally, you would create a condition for a conversion event and then call the track method when that condition is true.

The SDK requires you to provide your own unique user IDs for all of your activate and track calls. See User IDs for more details and best practices on what user IDs to provide.

You are responsible for creating and distributing OptimizelyManager around your application's components. The manager handles
setting up caches, datafile syncing, and more off of the main thread. See Initialization for more details.

Installation

The Python SDK is distributed through PyPi. To install, simply use pip or add optimizely-sdk to your requirements.txt.

The Java SDK is distributed through Bintray. The core-api and httpclient Bintray packages are optimizely-sdk-core-api and optimizely-sdk-httpclient respectively. You can find repository information as well as instructions on how to install the dependencies on Bintray. Gradle repository and dependency configurations are shown on the right. The SDK is compatible with Java 1.6 and above.

core-api requires org.slf4j:slf4j-api:1.7.16 and a supported JSON parser. We currently integrate with Jackson, GSON, json.org, and json-simple. If multiple of these parsers are available at runtime, one will be used by core-api according to the aforementioned selection priority. If none of these packages are already provided in your project's classpath, one will need to be added.

core-httpclient-impl requires org.apache.httpcomponents:httpclient:4.5.2 and provides an asynchronous event dispatcher which is described in the Event Dispatcher section. This library isn't required and you may provide a custom EventHandler implementation which uses a different networking stack.

Code Shrinking

Building with Proguard enabled to shrink your application requires adding rules to preserve code that might otherwise be discarded. Add these rules to your Proguard file, typically named proguard-rules.pro.

Our PHP SDK is available through Composer. To install simply composer require optimizely/optimizely-sdk or add optimizely/optimizely-sdk in the require section of your composer.json.

Requirements

PHP 5.5+

Our iOS and tvOS SDKs are available for distribution through CocoaPods, Carthage, or manual installation.

Requirements

iOS 8.0+ or tvOS 9.0+

Please note below that <platform> is used to represent the platform on which you are building your app. Currently, we support iOS and tvOS platforms. You should be pinning your dependency to a major or minor of the SDK to avoid breaking changes (from major version bumps) from affecting your build.

Unzip the framework, then drag the framework to your project in Xcode. Xcode should prompt you to select a target. Go to Build Phases and make sure that the framework is under the Link Binary with Libraries section.

Go to the General tab and add the framework to the Embedded Binaries section. If the Embedded Binaries section is not visible, add the framework in the Copy Files section (you can add this section in Build Settings).

The Apple store will reject your app if you have the universal framework installed as it includes simulator binaries. Therefore, a script to strip the extra binaries needs to be run before you upload the app. To do this, go to Build Phases and add a Run Script section by clicking the + symbol. Copy and paste the following script (make sure you replace the FRAMEWORK_NAME with the proper framework name):

If you choose to build the universal framework yourself, you can do so by building the OptimizelySDKiOS-Universal or OptimizelySDKTVOS-Universal schemes. Our third-party dependencies, which are pulled in as Git submodules, would need to be updated. To do so run the following commands:

git submodule init

followed by:

git submodule update

After building these schemes, the frameworks are output in the OptimizelySDKUniversal/generated-frameworks folder.

Our iOS and tvOS SDKs are available for distribution through CocoaPods, Carthage, or manual installation.

Requirements

iOS 8.0+ or tvOS 9.0+

Please note below that <platform> is used to represent the platform on which you are building your app. Currently, we support iOS and tvOS platforms.

Unzip the framework, then drag the framework to your project in Xcode. Xcode should prompt you to select a target. Go to Build Phases and make sure that the framework is under the Link Binary with Libraries section.

Go to the General tab and add the framework to the Embedded Binaries section. If the Embedded Binaries section is not visible, add the framework in the Copy Files section (you can add this section in Build Settings).

The Apple store will reject your app if you have the universal framework installed as it includes simulator binaries. Therefore, a script to strip the extra binaries needs to be run before you upload the app. To do this, go to Build Phases and add a Run Script section by clicking the + symbol. Copy and paste the following script (make sure you replace the FRAMEWORK_NAME with the proper framework name):

If you choose to build the universal framework yourself, you can do so by building the OptimizelySDKiOS-Universal or OptimizelySDKTVOS-Universal schemes. Our third-party dependencies, which are pulled in as Git submodules, would need to be updated. To do so run the following commands:

git submodule init

followed by:

git submodule update

After building these schemes, the frameworks are output in the OptimizelySDKUniversal/generated-frameworks folder.

The android-sdk module transitively depends on the datafile-handler, event-handler, user-profile, and shared modules. It also includes the latest version of gson and slf4j logger. Gradle can be used to exclude any dependency if you want to replace one with another version or implementation.

To add the android-sdk and all modules to your project simply include the following in your app's build.gradle in the dependencies block:

Check android-sdk repo on Bintray to determine the latest version. Please note, you should be pinning your dependency to a major or minor of the SDK to avoid breaking changes (from major version bumps) from affecting your build.

Example Code

repositories {
jcenter()
}
dependencies {
compile ('com.optimizely.ab:android-sdk:1.6.1') {
// exclude default implementation of slf4j if you want to provide
// your own. Multidex can fail if you have more than one
// implementation present. If you are having a multidex error
// this is most likely the issue.
exclude group: 'com.noveogroup.android', module:'android-logger'
}
}

Example Code

gem install optimizely-sdk

Example Code

Install-Package Optimizely.SDK

Example Code

npm install optimizely-server-sdk --save

Example Code

php composer.phar require optimizely/optimizely-sdk

Example Code

npm install optimizely-client-sdk --save

Initialization

The SDK defines an object — the Optimizely client — to simplify your interface with Optimizely. You can use the client to both activate experiments and track events.

Within your code, the client represents the state of your Optimizely project, so changes in your project will reflect in the client. The client accomplishes this by requiring you to provide your project's datafile during initialization.

After creating the client, you will have several options on how to manage the datafile within your application, which are detailed in the datafile article.

In addition to the datafile, you'll need to provide an EventHandler object as an argument to the Optimizely.builder function. You can use our default event handler implementation, as shown in the code at right, or provide your own implementation as described in the Event dispatcher section.

Optimizely.builder throws a checked exception, ConfigParseException, if the datafile provided is malformed or has an incorrect schema.

After creating the client, you will have several options on how to manage the datafile within your application, which are detailed in the datafile article.

You can optionally pass in a skipJSONValidation property as true to skip JSON schema validation of the datafile upon optimizely instantiation. Skipping JSON schema validation enhances instantiation performance. The skipJSONValidation property should only be used if the datafile is being pulled from the REST API or CDN.

In SDK versions 1.4.3 and above, the skipJSONValidation property is defaulted to true. If you'd like to enable JSON schema validation, you must pass in the skipJSONValidation property as false, and provide a JSONSchemaValidator, such as the one provided by the optimizely-server-sdk.

After creating the client, you will have several options on how to manage the datafile within your application, which are detailed in the datafile article.

You can optionally pass in a skipJSONValidation property as true to skip JSON schema validation of the datafile upon optimizely instantiation. Skipping JSON schema validation enhances instantiation performance. The skipJSONValidation property should only be used if the datafile is being pulled from the REST API or CDN.

The Optimizely class has complete feature parity with server side full stack SDKs (activate, getVariation, and track). In order to help to provide additional optimizations for mobile and OTT--automatic polling for the datafile, dispatching events in the background, and storing bucketing information--we built the DatafileManager, EventDispatcher, and UserProfile modules which are properties of the OPTLYManager.

The OPTLYClient class wraps the Optimizely core while also overriding the basic event dispatcher in the core and persisting bucketing information about users onto the device so they have a consistent experience between sessions even if traffic allocation is changed. An instance of this class will be returned from the init calls of an OPTLYManager instance.

The OPTLYManager class is the factory for OPTLYClient instances. It also owns the OPTLYDatafileManager which regularly polls the CDN checking for updated datafiles. The OPTLYManager also retains a reference to the last initialize OPTLYClient instance which you can get with the getOptimizely method.

In the manager's builder block, you can also specify your own datafile manager, event dispatcher, error handler, logger, and user profile as long as the implementation conforms to the OPTLYDatafileManager, OPTLYEventDispatcher, OPTLYErrorHandler, OPTLYEventDispatcher, OPTLYLogger, and OPTLYUserProfile protocols, respectively.

After creating the client, you will have several options on how to manage the datafile within your application, which are detailed in the datafile article.

In SDK versions 0.1.1 and above, you can optionally pass in a skip_json_validation property as True to skip JSON schema validation of the datafile upon optimizely instantiation. Skipping JSON schema validation enhances instantiation performance. The skip_json_validation property should only be used if the datafile is being pulled from the REST API or CDN.

After creating the client, you will have several options on how to manage the datafile within your application, which are detailed in the datafile article.

In SDK versions 0.1.1 and above, you can optionally pass in a skip_json_validation property as true to skip JSON schema validation of the datafile upon Project instantiation. Skipping JSON schema validation enhances instantiation performance. The skip_json_validation property should only be used if the datafile is being pulled from the REST API or CDN.

The Optimizely class has complete feature parity with server side full stack SDKs (activate, getVariation, and track). In order to help to provide additional optimizations for mobile and OTT--automatic polling for the datafile, dispatching events in the background, and storing bucketing information--we built the DatafileManager, EventDispatcher, and UserProfile modules which are properties of the OPTLYManager.

The OPTLYClient class wraps the Optimizely core while also overriding the basic event dispatcher in the core and persisting bucketing information about users onto the device so they have a consistent experience between sessions even if traffic allocation is changed. An instance of this class will be returned from the init calls of an OPTLYManager instance.

The OPTLYManager class is the factory for OPTLYClient instances. It also owns the OPTLYDatafileManager which regularly polls the CDN checking for updated datafiles. The OPTLYManager also retains a reference to the last initialize OPTLYClient instance which you can get with the getOptimizely method.

In the manager's builder block, you can also specify your own datafile manager, event dispatcher, error handler, logger, and user profile as long as the implementation conforms to the OPTLYDatafileManager, OPTLYEventDispatcher, OPTLYErrorHandler, OPTLYEventDispatcher, OPTLYLogger, and OPTLYUserProfile protocols, respectively.

Example Code

Initialization methods

Before you begin to write the code for your Optimizely client initialization, consider the needs of your application.

The SDK offers two methods for initializing the Optimizely client: synchronous and asynchronous. The behavioral difference between the two methods is whether your application pings the Optimizely servers for a new datafile during initialization of the Optimizely client. The functional difference between the two methods is whether your app prioritizes accuracy or speed.

Asynchronous Initialization

The asynchronous initialization method prioritizes accuracy over speed. During initialization of the Optimizely client, your app will request the newest datafile from the CDN servers. Requesting the newest datafile ensures that your app will always use the most current project settings, but it also means your app cannot initialize a client until it downloads a new datafile, discovers the datafile has not changed, or until the request times out. This process takes time.

Synchronous Initialization

The synchronous initialization method prioritizes speed over accuracy. Instead attempting to download a new datafile every time you initialize an Optimizely client, your app will use a local version of the datafile. This local datafile can be cached from a previous network request (see datafile polling) or embedded within your app (see datafile bundling).

The OPTLYManager class provides several options for initializing your client depending where you are running experiments in the app. See OPTLYManager.h for full details.

Synchronous Initialization

The synchronous initialize method creates a client using a datafile from the local cache. This is the most common way that customers choose to initialize the Optimizely SDK. Synchronous initialization optimizes for speed, by initializing the SDK using the latest cached datafile. If no datafile is saved or there is an error retrieving the saved datafile, then the bundled datafile is used. If no bundled datafile is provided by the developer, then the SDK will return a dummy client.

Asynchronous Initialization

The asynchronous initialization allows the user to maximize having the most up-to-date experiment data by attempting to download the latest datafile. In the case that there is an error in the datafile download, the latest cached datafile (if one exists) is used. If there are no updates in the datafile, then the datafile is not downloaded and the latest cached datafile is used. If the cached datafile fails to load, then the bundled datafile is used.

This section is not applicable for the SDK.

This section is not applicable for the SDK.

This section is not applicable for the SDK.

The OPTLYManager class provides several options for initializing your client depending where you are running experiments in the app. See OPTLYManager.h for full details.

Synchronous Initialization

The synchronous initialize method creates a client using a datafile from the local cache. Synchronous initialization maximizes for speed by allowing the user to initialize the client immediately with the latest cached datafile. If no datafile is saved or there is an error retrieving the saved datafile, then the bundled datafile is used. If no bundled datafile is provided by the developer, then the SDK will return a dummy client.

Asynchronous Initialization

The asynchronous initialization allows the user to maximize having the most up-to-date experiment data by attempting to download the latest datafile. In the case that there is an error in the datafile download, the latest cached datafile (if one exists) is used. If there are no updates in the datafile, then the datafile is not downloaded and the latest cached datafile is used. If the cached datafile fails to load, then the bundled datafile is used.

Create a manager

For the SDK, instead of directly initializing the Optimizely client, you first create an Optimizely manager, which you will later use to initialize Optimizely clients.

The Optimizely manager is where you can describe how the Optimizely client will function. For example, you will use the Optimizely client to record when a user performs an action on your site (an event), but you can first specify how often those events will be sent to Optimizely's servers with the Optimizely manager.

You should create the Optimizely manager inside Application#onCreate().

1. Get your project ID

To build a manager, you'll need to provide your project ID, which you can find by navigating to the Settings page in the Optimizely web app. The Project ID will be displayed on the Datafile tab under the Details section.

2. Build the manager

To build the manager, use the builder function and pass the project ID: OptimizelyManager.builder(PROJECT_ID).

3. Set parameters (optional)

While building the manager, you can optionally set a couple parameters.

Datafile polling (highly recommended): Set how often the manager fetches and caches a new datafile: .withDatafileDownloadInterval(long interval).

Create a client

The Optimizely client represents the state of your Optimizely project within your code. In more technical terms, the OptimizelyClient class wraps the Optimizely core and overrides the basic event dispatcher and datafile handler in the core.

Synchronous initialization

When you initialize a client synchronously, the Optimizely manager first looks for a cached datafile. If one is available, the manager will use it to complete the client initialization. If the manager cannot find a cached datafile, the manager will look for a bundled datafile. If the manager finds a the bundled datafile, it will use the datafile to complete the client initialization. If the manager cannot find a bundled datafile, the manager will be unable to initialize the client.

To initialize an OptimizelyClient object synchronously, call OptimizelyManager#initialize() and provide two arguments:

Asynchronous initialization

Initializing a client asynchronously executes like the synchronous initialization, except the Optimizely manager will first attempt to download the newest datafile. This network activity is what causes an asynchronous initialization to take longer to complete.

If the network request returns an error (such as when network connectivity is unreliable) or if the manager discovers that the cached datafile is identical to the newest datafile, the manager will follow the synchronous initialization path. If manager discovers that the datafile has been updated and now differs from the cached datafile, the manager will download the new datafile and use it to initialize the client.

To initialize an OptimizelyClient object asynchronously, call OptimizelyManager#initialize() and three arguments:

Retrieve a client

Each OptimizelyManager object retains a reference to its most recently initialized OptimizelyClient, and running OptimizelyManager#getOptimizely() will return that client instance.

Note that OptimizelyManager#getOptimizely() is annotated @NonNull and will always return an OptimizelyClient instance.

If OptimizelyManager#getOptimizely() is called before any OptimizelyClient objects have been initialized, a "dummy" OptimizelyClient instance will be created and returned. This dummy instance logs errors when any of its methods are used.

The SDK does not have documentation for Optimizely client retrieval here.

The SDK does not have documentation for Optimizely client retrieval here.

The SDK does not have documentation for Optimizely client retrieval here.

The SDK does not have documentation for Optimizely client retrieval here.

The SDK does not have documentation for Optimizely client retrieval here.

The SDK does not have documentation for Optimizely client retrieval here.

The SDK does not have documentation for Optimizely client retrieval here.

The SDK does not have documentation for Optimizely client retrieval here.

The SDK does not have documentation for Optimizely client retrieval here.

Datafile bundling

This section applies only if your app uses the datafile handler module included in the Optimizely Android SDK.

When your customer opens your app for the first time after installing or updating it, the Optimizely SDK initializes the Optimizely manager, and during its initialization, the Optimizely manager attempts to pull the newest datafile from the CDN servers.

If your app is unable to contact Optimizely's CDN servers to download a datafile, the Optimizely manager can substitute a datafile that you included ("bundled") when you created your app. By bundling a datafile within your app, you ensure that the Optimizely manager can initialize without waiting for a response from our CDN servers.

By bundling a datafile, you can prevent poor network connectivity from causing your app to hang or crash while it loads.

Note: Because optionally revering to a bundled datafile happens during the Optimizely manager's initialization, the process will complete before the manager initializes the Optimizely client. Thus, datafile bundling is compatible with both synchronous and asynchronous initialization of the Optimizely client.

The does not use datafile bundling.

The does not use datafile bundling.

The does not use datafile bundling.

The does not use datafile bundling.

In both the initialization cases described above, the SDK will fall back to the bundled datafile. We refer to the “bundled” datafile as the datafile stored on the device and provided in the builder during the manager initialization.

The does not use datafile bundling.

The does not use datafile bundling.

The does not use datafile bundling.

In both the initialization cases described above, the SDK will fall back to the bundled datafile. We refer to the “bundled” datafile as the datafile stored on the device and provided in the builder during the manager initialization.

Datafile polling

This section applies only if your app uses the datafile handler module included in the Optimizely Android SDK.

During its initialization, the Optimizely manager attempts to pull the newest datafile from the CDN servers. After this, the Optimizely manager will periodically check for and pull a new datafile at the time interval you set during its initialization. The process of checking for and downloading a new datafile is called datafile polling.

To ensure your app always has the latest datafile, you should enable periodic datafile polling.

To enable periodic datafile polling, set a value for withDatafileDownloadInterval when you initialize the Optimizely manager. This value is the number of seconds the Optimizely manager will wait between datafile polling attempts. If you do not set a value for .withDatafileDownloadInterval, the Optimizely manager will check for a new datafile during initialization, but it will not check again.

Notes

Updated datafiles do not take effect until your app is restarted or when you re-initialize the Optimizely manager. This implementation strategy allows the Optimizely SDK to change while the app is running without causing nondeterministic behavior.

By default, the Optimizely manager will only check for new datafiles when the SDK is active and the app is running. You can configure datafile polling by adding the DatafileRescheduler to your app's manifest. For more information, see Breaking Changes in the 1.4.0-1.5.1 sections of the Optimizely Android X Changelog. For Android Oreo (8.x) and later where polling is set up to also run in background and foreground: If you don't want background polling you need to unschedule your datafile downloads using the scheduler.

The does not use datafile polling.

The does not use datafile polling.

The does not use datafile polling.

The does not use datafile polling.

It is recommended that you enable periodic datafile manager polling so that you will always try to save the latest datafile. When the datafile manager is first created, it will try to pull a new datafile. The datafile manager will wait for the duration of the datafile manager download interval before attempting the next datafile download. The new datafile, however, is not reflected until the next launch or when you initialize the manager again—this is enforced to prevent nondeterministic behavior if the Optimizely SDK changes while the app is running.

In order to enable the datafile manager polling, set a value for datafileManagerDownloadInterval when creating the datafile manager. The time unit is in seconds. The datafile polling behavior depends on your SDK and Android versions:

If you're using SDK 1.4.x and Android Nougat (7.x), datafile polling is never done in the background.

If you're using SDK 1.5.x and Oreo (8.x) or later, you can configure the time of datafile polling to your preferences.

The does not use datafile polling.

The does not use datafile polling.

The does not use datafile polling.

It is recommended that you enable periodic datafile manager polling so that you will always try to save the latest datafile. When the datafile manager is first created, it will try to pull a new datafile. The datafile manager will wait for the duration of the datafile manager download interval before attempting the next datafile download. The new datafile, however, is not reflected until the next launch or when you initialize the manager again—this is enforced to prevent nondeterministic behavior if the Optimizely SDK changes while the app is running.

In order to enable the datafile manager polling, set a value for datafileManagerDownloadInterval when creating the datafile manager. The time unit is in seconds. The datafile polling is never done in the background (only when the SDK is up and running).

Datafile

The SDKs are required to read and parse a datafile at initialization.

The datafile is in JSON format and compactly represents all of the instructions needed to activate experiments and track events in your code without requiring any blocking network requests. For example, the datafile displayed on the right represents an example project from the Getting started guide and the basic usage above.

Unless you are building your own SDK, there shouldn't be any need to interact with the datafile directly. Our SDKs should provide all the interfaces you need to interact with Optimizely after parsing the datafile.

Access datafile via CDN

You can fetch the datafile for your Optimizely project from Optimizely's CDN. For example, if the ID of your project is 12345 you can access the file at the link below:

Synchronization

To stay up to date with your experiment configuration in Optimizely, the SDK needs to periodically synchronize its local copy of the datafile.

To keep the datafile in sync, we recommend setting up a webhook to notify your servers when the datafile changes and then retrieving the latest version. Alternatively, you can pull an updated datafile from the CDN or REST API at a regular interval.

To keep the datafile in sync, we recommend setting up a webhook to notify your servers when the datafile changes and then retrieving the latest version. Alternatively, you can pull an updated datafile from the CDN or REST API at a regular interval.

To keep the datafile in sync, we recommend setting up a webhook to notify your servers when the datafile changes and then retrieving the latest version. Alternatively, you can pull an updated datafile from the CDN or REST API at a regular interval.

To keep the datafile in sync, we recommend setting up a webhook to notify your servers when the datafile changes and then retrieving the latest version. Alternatively, you can pull an updated datafile from the CDN or REST API at a regular interval.

To keep the datafile in sync, we recommend setting up a webhook to notify your servers when the datafile changes and then retrieving the latest version. Alternatively, you can pull an updated datafile from the CDN or REST API at a regular interval.

To keep the datafile in sync, we recommend setting up a webhook to notify your servers when the datafile changes and then retrieving the latest version. Alternatively, you can pull an updated datafile from the CDN or REST API at a regular interval.

To keep the datafile in sync, we recommend setting up a webhook to notify your servers when the datafile changes and then retrieving the latest version. Alternatively, you can pull an updated datafile from the CDN or REST API at a regular interval.

Our Android SDK includes an OptimizelyManager class that can be used to handle this synchronization for you. It periodically downloads the latest datafile from Optimizely asynchronously.

You should create an OptimizelyManager inside of Application#onCreate(). The example shows how to store the manager as a field in your Application subclass and provide a getter for it. This getter will be used to get the manager in Activities and Services. OptimizelyManager can also be used with dependency injection frameworks such as Dagger and Guice.

OptimizelyManager.builder() is used to make an OptimizelyManager. The builder allows you to configure the Android SDK.

.withEventDispatchInterval(long interval) Controls how often the Android SDK will attempt to dispatch events to Optimizely results if there are events stored that failed to dispatch initially. Dispatch happens in an Android Service and will occur even if the app is not opened back up by the user. The service will only be scheduled when events that failed to dispatch are stored. It will be unscheduled when all stored events are flushed. Default:1 day, in seconds

.withDataFileDownloadInterval(long interval) Controls how often the Android SDK will attempt to sync the datafile. This sync happens via an Android Service and helps ensure that the Android SDK's cached datafile is up to date. This service runs indefinitely on interval specified.

Datafile polling is disabled by default. You can enable it by setting the download interval. This sets up datafile downloads at the specified interval in both foreground and background. In pre-Oreo (7.x and earlier) versions of Android, the service uses a timer and context.startService (the interval in seconds is always honored). In Oreo (8.x) and later versions of Android, the JobScheduler is used to queue work items (the OS determines when and if your service will run).

You can configure datafile polling by adding the DatafileRescheduler to your app's manifest. For more information, see Breaking Changes in the 1.4.0-1.5.1 sections of the Optimizely Android X Changelog.

In order to have these handlers restarted if there is a reboot, reinstall, or for events wifi becomes available, you need to add intent filters to your application manifest. Please see the android 'test_app'

Working with multiple projects

You can have multiple Optimizely projects running in the same app. This can be accomplished by having one OptimizelyManager instance per project ID. Datafile caches, scheduling settings, and Optimizely objects are managed per project ID.

Note: You do not need to use OptimizelyManager. You are free to fetch the datafile on your own and create an Optimizely object via Optimizely.builder(String dataFile, EventHandler). You must also use the included EventHandler or implement your own. This is explained in detail below.

The OPTLYDatafileManager can be used to synchronize the datafile for you. The following datafile implementations are available:

OPTLYDatafileManagerDefault: Periodically downloads the datafile from Optimizely in the background (the default interval is set to 2 minutes). The datafile is saved if it has been updated. However, the SDK does not see this updated datafile until a new client is initialized with the new datafile. The OptimizelySDKiOS and OptimizelySDKTVOS frameworks use this implementation out of the box.

OPTLYDatafileManagerBasic: Basic implementation that downloads the datafile but does not include polling and saving. The OptimizelySDKShared framework uses this implementation out of the box.

OPTLYDatafileMangerNoOp: No-op implementation that does not attempt to download the datafile.

The OPTLYDatafileManager can be used to synchronize the datafile for you. The following datafile implementations are available:

OPTLYDatafileManagerDefault: Periodically downloads the datafile from Optimizely in the background (the default interval is set to 2 minutes). The datafile is saved if it has been updated. However, the SDK does not see this updated datafile until a new client is initialized with the new datafile. The OptimizelySDKiOS and OptimizelySDKTVOS frameworks use this implementation out of the box.

OPTLYDatafileManagerBasic: Basic implementation that downloads the datafile but does not include polling and saving. The OptimizelySDKShared framework uses this implementation out of the box.

OPTLYDatafileMangerNoOp: No-op implementation that does not attempt to download the datafile.

Webhooks

If you are managing your datafile from a server-side application, we recommend configuring webhooks to maintain the most up-to-date version of the datafile. Your supplied endpoint will be sent a POST request whenever the respective project is modified. Anytime the datafile is updated, you must re-instantiate the Optimizely object in the SDK for the changes to take affect.

You can setup a webhook in your project settings and add the URL the webhook service should ping.

The webhook payload structure is shown at right. As of today, we support one event type, project.datafile_updated.

If your app and backend have push messaging set up, you can keep your datafile in sync. You can send a push message from your server to devices running your app telling them to update the datafile. You will need to build a new OptimizelyClient object with the OptimizelyManager#initialize method to get a new object backed by the new datafile. Read more in the Initialization section.

If your app and backend have push messaging set up, you can keep your datafile in sync. You can send a push message from your server to devices running your app telling them to update the datafile. You will need to build a new OPTLYClient object with one of the OPTLYManager.init() methods to get a new object backed by the new datafile. Read more in the Initialization section.

If your app and backend have push messaging set up, you can keep your datafile in sync. You can send a push message from your server to devices running your app telling them to update the datafile. You will need to build a new OPTLYClient object with one of the [OPTLYManager init] methods to get a new object backed by the new datafile. Read more in the Initialization section.

Securing webhooks

When you create a webhook, Optimizely generates a secret token that is used to create a hash signature of webhook payloads. Webhook requests include this signature in a header X-Hub-Signature that can be used to verify the request originated from Optimizely.

You will only be able to view a webhook's secret token once immediately after creation. If you forget a webhook's secret token, you'll need to regenerate it on your project settings page.

The X-Hub-Signature header contains a SHA1 HMAC hexdigest of the webhook payload, using the webhook's secret token as the key and prefixed with sha1=. While the way you verify this signature will vary depending on the language of your codebase, we've provided a Flask reference implementation.

Note: We strongly recommend that you use a constant time string comparison function such as Python's hmac.compare_digest or Rack's secure_compare instead of the == operator when verifying webhook signatures. This prevents timing analysis attacks.

Versioning

Note: Datafile versioning is not applicable for this SDK.

Note: Datafile versioning is not applicable for this SDK.

Note: Datafile versioning is not applicable for this SDK.

Note: Datafile versioning is not applicable for this SDK.

Note: Datafile versioning is not applicable for this SDK.

Note: Datafile versioning is not applicable for this SDK.

Note: Datafile versioning is not applicable for this SDK.

For iOS, tvOS and Android projects, the datafile is versioned so that we can maintain backwards compatibility with SDKs that have been released out into the wild with older versions of the datafile. This will ensure that in the event our datafile schema changes, experiments will still run in apps that have not been updated with the latest version of the SDK.

All versions of the datafile for your Optimizely project are accessible via the CDN. For example, if the ID of your project is 12345 you can access v3 the datafile at the link below:

https://cdn.optimizely.com/public/12345/datafile_v3.json

We will upload every supported version of the datafile to our CDN so that SDKs that are pegged to an older version will be able to retrieve a datafile that is compatible with that SDK.

If you are using OPTLYManager (for iOS or tvOS) or OptimizelyManager (for Android) to manage datafile updates, these will gracefully handle datafile versioning. However, if you are managing the datafile on your own, then you have to make sure to fetch the correct version of the datafile according to the SDK version you are using.

Experiments

Use the activate function to run an experiment at any point in your code.

The activate function requires an experiment key and a user ID. The experiment key should match the experiment key you provide when setting up the experiment in the Optimizely web portal. The user ID is a string to uniquely identify the participant in the experiment (read more in User IDs below).

The activate function returns which variation the current user is assigned to. If the experiment is running and the user satisfies audience conditions for the experiment, the function returns a variation based on a deterministic murmur hash of the provided experiment key and user ID. This function also respects whitelisting and and user profiles.

If any of the conditions necessary for the experiment are not met, activate returns None.

If any of the conditions necessary for the experiment are not met, activate returns null.

If any of the conditions necessary for the experiment are not met, activate returns null.

If any of the conditions necessary for the experiment are not met, activate returns nil.

If any of the conditions necessary for the experiment are not met, activate returns null.

If any of the conditions necessary for the experiment are not met, activate returns null.

If any of the conditions necessary for the experiment are not met, activate returns null.

If any of the conditions necessary for the experiment are not met, activate returns nil.

If any of the conditions necessary for the experiment are not met, activate returns nil.

If any of the conditions necessary for the experiment are not met, activate returns null.

Make sure that your code adequately deals with the case when the experiment is not activated i.e. execute the default variation.

The activate function also sends an event to Optimizely to record that the current user has been exposed to the experiment. You should call activate at the point you want to record an experiment exposure to Optimizely. If you don't want to record an experiment exposure, you can use an alternative function below to get the variation.

User attributes

If you'd like to be able to segment your experiment data based on attributes of your users, you should include the optional attributes argument when activating experiments. Optimizely will include these attributes in the recorded event data so you can segment them on the Optimizely results page.

Passing attributes will also allow you to target your experiments to a particular audience you've defined in Optimizely. If the provided experiment is targeted to an audience, Optimizely will evaluate whether the user falls in an audience that is associated with the experiment before bucketing.

For more information on managing audiences and attributes, see our Optiverse article.

Example Code

Variation assignments

If you would like to retrieve the variation assignment for a given experiment and user without sending a network request to Optimizely, use the code shown on the right. This function has identical behavior to activate except that no event is dispatched to Optimizely.

You may want to use this if you're calling activate in a different part of your application, or in a different part of your stack, and you want to fork code for your experiment in multiple places. It is still necessary to call activate at least once to register that the user was exposed to the experiment.

Example Code

Example Code

// Get the active variation for an experiment for the provided user
OPTLYVariation *variation = [optimizely variation:@"my_experiment"
userId:@"user123"];

Example Code

// Get the active variation for an experiment for the provided user
let variation: OPTLYVariation? = optimizely?.variation("my_experiment", userId:"user123")

Exclusion groups

You can use Exclusion Groups to keep your experiments mutually exclusive and eliminate interaction effects. For more information on how to set up exclusion groups in the Optimizely UI, see our Optiverse article.

Experiments that are part of a group have the exact same interface in the SDK: you can call activate and track like any other experiment. The SDK will ensure that two experiments in the same group will never be activated for the same user.

Events

You can easily track conversion events from your code using the track function.

The track function requires an event key and a user ID. The event key should match the event key you provided when setting up events in the Optimizely web portal. The user ID should match the user ID provided in the activate function.

The track function can be used to track events across multiple experiments, and will be counted for each experiment only if activate has previously been called for the current user.

To enable segmentation of metrics on the Optimizely results page, you'll also need to pass the same user attributes you used in the activate call.

For offline event tracking and other advanced use cases, you can also use the Event API.

Note: The Optimizely experiment results page will only count events that are tracked afteractivate has been called. If you are not seeing results on the Optimizely results page, make sure that you are calling activate before tracking conversion events.

Event tags

Event tags are contextual metadata about conversion events that you track.

You can use event tags to attach any key/value data you wish to events. For example, for a product purchase event you may want to attach a product SKU, product category, order ID, and purchase amount. Event tags can be strings, integers, floating point numbers, or Boolean values.

You can include event tags with an optional argument in track as shown on the right.

Event tags are distinct from user attributes which should be reserved for user-level targeting and segmentation. Event tags do not affect audiences or the Optimizely results page, and do not need to be registered in the Optimizely web interface.

Event tags are accessible via raw data export in the event_features column. You should include any event tags you need to reconcile your conversion event data with your data warehouse.

Reserved tags

The following tag keys are reserved and will be included in their corresponding fields in the Optimizely event API payload. They're bundled into event tags for your convenience. Use them if you'd like to benefit from specific reporting features such as revenue metrics or numeric metrics.

revenue - An integer value that is used to track the Revenue metric for your experiments, aggregated across all conversion events. Revenue is recorded in cents: if you'd like to record a revenue value of $64.32 use 6432. Any event you want to track revenue for will need to be added as a Metric to your experiment. You can use the Overall Revenue Metric to aggregate multiple Metrics tracking separate revenue events. The Overall Revenue Event won't track any revenue unless there are other Metrics in your experiment tracking an increase or decrease in total revenue – it won't work on its own.

value - A floating point value that is used to track a custom value for your experiments.

Tracking with other SDKs

You can use any of our SDKs to track events, so you can run experiments that span multiple applications, services, or devices. All of our SDKs have the same bucketing and targeting behavior so you'll see the exact same output from experiment activation and tracking, provided you are using the same datafile and user IDs.

If you plan on using multiple SDKs for the same project, make sure that all SDKs are sharing the same datafile and user IDs.

User IDs

User IDs are used to uniquely identify the participants in your experiments. You can supply any string you wish for user IDs depending on your experiment design.

For example, if you're running experiments on anonymous users, you can use a 1st party cookie or device ID to identify each participant. If you're running experiments on known users, you can use a universal user identifier (UUID) to identify each participant. If you're using UUIDs then you can run experiments that span multiple devices or applications and ensure that users have a consistent treatment.

User IDs don't necessarily need to correspond to individual users. If you're running experiments in a B2B SaaS application, you may want to pass account IDs to the SDK to ensure that every user in a given account has the same treatment.

Below are some additional tips for using user IDs.

Ensure user IDs are unique: It is essential that user IDs are unique among the population you are using for experiments. Optimizely will bucket users and provide experiment metrics based on this user ID that you provide.

Anonymize user IDs: The user IDs you provide will be sent to Optimizely servers exactly as you provide them. You are responsible for anonymizing any personally identifiable data such as email addresses in accordance with your company's policies.

Use IDs from 3rd party platforms: If you are measuring the impact of your experiments in a 3rd party analytics platform, we recommend leveraging the same user ID from that platform. This will help ensure data consistency and make it easier to reconcile events between systems.

Use one namespace per project: Optimizely generally assumes a single namespace of user IDs for each project. If you are using multiple different conventions for user IDs in a single project (e.g. anonymous visitor IDs for some experiments and UUIDs for others) then Optimizely will be unable to enforce rules such as mutual exclusivity between experiments.

Use either logged-out vs. logged-in IDs: We do not currently provide a mechanism to alias logged-out IDs with logged-in IDs. If you are running experiments that span both logged-out and logged-in states (e.g. experiment on a signup funnel and track conversions after the user has logged in), you must persist logged-out IDs for the lifetime of the experiment.

Bucketing IDs ʙᴇᴛᴀ

Bucketing ID is a beta feature intended to support customers who want to assign variations with different identifier than they use to count visitors. For example, a company might want to assign variations by account ID while counting visitors by user ID. We're investigating the implications of Bucketing IDs on results analysis, and we'd love your feedback! If you want to participate in this beta release, open a ticket with our support team.

By default, Optimizely assigns users to variations, i.e., Optimizely "buckets" users, based on submitted user IDs. You can change this behavior by including a bucketing ID.

With a bucketing ID, you decouple user bucketing from user identification. Users who have the same bucketing ID are put into the same bucket and are exposed to the same variation.

To include a bucketing ID, use the reserved key $opt_bucketing_id in the attributes parameter of SDK API method calls. (This technique avoids additional parameter requirements in the SDKs' methods.) The bucketing ID serves as the seed of the hash value used to assign the user to a variation. Users with the same bucketing ID will have the same hash value, which is why they are exposed to the same variation.

To include a bucketing ID, use the reserved key bucketId in the attributes parameter of SDK API method calls. (This technique avoids additional parameter requirements in the SDKs' methods.) The bucketing ID serves as the seed of the hash value used to assign the user to a variation. Users with the same bucketing ID will have the same hash value, which is why they are exposed to the same variation.

To include a bucketing ID, use the reserved key $opt_bucketing_id in the attributes parameter of SDK API method calls. (This technique avoids additional parameter requirements in the SDKs' methods.) The bucketing ID serves as the seed of the hash value used to assign the user to a variation. Users with the same bucketing ID will have the same hash value, which is why they are exposed to the same variation.

To include a bucketing ID, use the reserved key $opt_bucketing_id in the attributes parameter of SDK API method calls. (This technique avoids additional parameter requirements in the SDKs' methods.) The bucketing ID serves as the seed of the hash value used to assign the user to a variation. Users with the same bucketing ID will have the same hash value, which is why they are exposed to the same variation.

To include a bucketing ID, use the reserved key $opt_bucketing_id in the attributes parameter of SDK API method calls. (This technique avoids additional parameter requirements in the SDKs' methods.) The bucketing ID serves as the seed of the hash value used to assign the user to a variation. Users with the same bucketing ID will have the same hash value, which is why they are exposed to the same variation.

To include a bucketing ID, use the reserved key $opt_bucketing_id in the attributes parameter of SDK API method calls. (This technique avoids additional parameter requirements in the SDKs' methods.) The bucketing ID serves as the seed of the hash value used to assign the user to a variation. Users with the same bucketing ID will have the same hash value, which is why they are exposed to the same variation.

To include a bucketing ID, use the reserved key $opt_bucketing_id in the attributes parameter of SDK API method calls. (This technique avoids additional parameter requirements in the SDKs' methods.) The bucketing ID serves as the seed of the hash value used to assign the user to a variation. Users with the same bucketing ID will have the same hash value, which is why they are exposed to the same variation.

To include a bucketing ID, use the reserved key $opt_bucketing_id in the attributes parameter of SDK API method calls. (This technique avoids additional parameter requirements in the SDKs' methods.) The bucketing ID serves as the seed of the hash value used to assign the user to a variation. Users with the same bucketing ID will have the same hash value, which is why they are exposed to the same variation.

To include a bucketing ID, use the reserved key bucketingId in the attributes parameter of SDK API method calls. (This technique avoids additional parameter requirements in the SDKs' methods.) The bucketing ID serves as the seed of the hash value used to assign the user to a variation. Users with the same bucketing ID will have the same hash value, which is why they are exposed to the same variation.

To include a bucketing ID, use the reserved key bucketId in the attributes parameter of SDK API method calls. (This technique avoids additional parameter requirements in the SDKs' methods.) The bucketing ID serves as the seed of the hash value used to assign the user to a variation. Users with the same bucketing ID will have the same hash value, which is why they are exposed to the same variation.

Using a bucketing ID does not affect the user ID. Event data submissions will continue to include user IDs. With the exception of assigning users to specific variations, features that rely on user IDs behave the same regardless of the presence of a separate bucketing ID. If you do not pass a bucketing ID to the attributes parameter, users will be bucketed by user IDs, which is the default method.

Notes

The bucketing ID is not persisted.

Bucketing IDs are not compatible with Optimizely's Rollouts feature.

Note: Using Bucketing IDs could skew your results as you will no longer be running a true A/B test.

QA & Debugging

There are several tools available for QAing experiments and debugging the SDK's behavior.

Whitelists

You can use Whitelisting to force users into specific variations for QA purposes. For more information on how to set up whitelists in the Optimizely UI, see our Optiverse article.

Whitelists are included in your datafile in the forcedVariations field. You don't need to do anything differently in the SDK; if you've set up a whitelist, experiment activation will force the variation output based on the whitelist you've provided. Whitelisting overrides audience targeting and traffic allocation. Whitelisting does not work if the experiment is not running.

Forced Bucketing

The forced bucketing feature allows you to force a user into a variation by calling a method in the SDK. This feature is particularly useful for the purpose of testing as it allows you to set the variation on the client in real time, eliminating the uncertainty and latency of a datafile download.

Forced bucketing is similar to whitelisting in that it allows you to force a user into a specific variation. However, what differentiates forced bucketing from whitelisting is that you set the variation in the SDK and not on the Optimizely web dashboard (therefore, eliminating the dependency on a datafile download) and you are not limited to how many users can be forced into a variation as you are when whitelisting. It is important to note that the forcedVariations field in the datafile is only related to whitelisted variations and not to variations set by this API.

The example code demonstrates how to use the forced bucketing API. An experiment key, user ID, and variation key are passed into the set method. The variation set will be cached and used by all SDK API methods, including activate and track, for that session (i.e., the lifetime of the Optimizely SDK client instance). Variations are overwritten with each set method call. In order to clear the forced variations so that the normal bucketing flow can occur, simply pass null as the variation key parameter. A corresponding getter method, which passes in the experiment key and user ID, will allow you to get the variations that you forced a user into for a particular experiment.

Forced bucketing variations will take precedence over whitelisted variations, variations saved in the User Profile Service (if one exists), and the normal bucketed variation. Events sent to our results backend will proceed as normal when forced bucketing is enabled.

Variables

Live Variables are supported in the mobile SDKs.

Use Live Variables to parameterize your app with features you'd like to experiment with in real-time.
Variables can be declared and initialized once in your app. After deploying those variables to production, you can subsequently run unlimited experiments on different values of those variables without doing another code deploy. Variables can be controlled from the Optimizely UI so you can roll out features and tweak the behavior of your app in real-time. See our Optiverse article for more details on how to control variables from the Optimizely user interface.
The examples at right show how to retrieve the value of a variable for the current user using the Android SDK. We currently support Boolean, floating point, integer, and string variable types. Each of the getVariable functions includes the following arguments:

variableKey: A unique name of the variable, as defined in the Optimizely UI.

userId: A unique identifier for the current user.

attributes (optional): A map of user attributes and values for the current user.

activateExperiment: If set to true, record impression events to Optimizely for the experiment(s) that include this variable.

If the current user is part of an experiment that includes the specified variable, the function returns the value of the variable, for the variation that user was assigned to. If the user is not in an experiment that includes the variable, but the variable exists in the datafile, the function returns the default value from the datafile. Otherwise, the function returns null. Your code must correctly handle cases where a null value is returned.

Note: If your experiment can be defined completely by a set of variables, it is not necessary to call activate to run the experiment in your code. You can just add getters for each of the relevant variables and subsequently create new experiments on the fly. The activateExperiment parameter can be used to ensure that impressions are correctly recorded in Optimizely in the event you are not using activate. If you are using activate for an experiment that includes the live variable then you can set this to false to avoid redundant network requests.

Use Live Variables to parameterize your app with features you'd like to experiment with in real-time.
Variables can be declared and initialized once in your app. After deploying those variables to production, you can subsequently run unlimited experiments on different values of those variables without doing another code deploy. Variables can be controlled from the Optimizely UI so you can roll out features and tweak the behavior of your app in real-time. See our Optiverse article for more details on how to control variables from the Optimizely user interface.
The examples at right show how to retrieve the value of a variable for the current user using the Objective-C SDK. We currently support Boolean, floating point, integer, and string variable types. Each of the getVariable functions includes the following arguments:

variableKey: A unique name of the variable, as defined in the Optimizely UI.

userId: A unique identifier for the current user.

attributes (optional): A map of user attributes and values for the current user.

activateExperiment: If set to true, record impression events to Optimizely for the experiment(s) that include this variable.

error (optional): An error value to return if the variable key is not present in the datafile.

If the current user is part of an experiment that includes the specified variable, the function returns the value of the variable, for the variation that user was assigned to. If no matching variable key is found, then default value is returned if it exists. Otherwise, nil is returned. If an error is found, a warning message is logged, and an error will be propagated to the user.

Note: If your experiment can be defined completely by a set of variables, it is not necessary to call activate to run the experiment in your code. You can just add getters for each of the relevant variables and subsequently create new experiments on the fly. The activateExperiment parameter can be used to ensure that impressions are correctly recorded in Optimizely in the event you are not using activate. If you are using activate for an experiment that includes the live variable then you can set this to false to avoid redundant network requests.

Use Live Variables to parameterize your app with features you'd like to experiment with in real-time.
Variables can be declared and initialized once in your app. After deploying those variables to production, you can subsequently run unlimited experiments on different values of those variables without doing another code deploy. Variables can be controlled from the Optimizely UI so you can roll out features and tweak the behavior of your app in real-time. See our Optiverse article for more details on how to control variables from the Optimizely user interface.
The examples at right show how to retrieve the value of a variable for the current user using the Objective-C SDK. We currently support Boolean, floating point, integer, and string variable types. Each of the getVariable functions includes the following arguments:

variableKey: A unique name of the variable, as defined in the Optimizely UI.

userId: A unique identifier for the current user.

attributes (optional): A map of user attributes and values for the current user.

activateExperiment: If set to true, record impression events to Optimizely for the experiment(s) that include this variable.

error (optional): An error value to return if the variable key is not present in the datafile.

If the current user is part of an experiment that includes the specified variable, the function returns the value of the variable, for the variation that user was assigned to. If no matching variable key is found, then default value is returned if it exists. Otherwise, nil is returned. If an error is found, a warning message is logged, and an error will be propagated to the user.

Note: If your experiment can be defined completely by a set of variables, it is not necessary to call activate to run the experiment in your code. You can just add getters for each of the relevant variables and subsequently create new experiments on the fly. The activateExperiment parameter can be used to ensure that impressions are correctly recorded in Optimizely in the event you are not using activate. If you are using activate for an experiment that includes the live variable then you can set this to false to avoid redundant network requests.

The SDK provides default implementations of event dispatching, logging, and error handling out of the box, but we encourage you to override the default behavior if you have different requirements in your production application. If you'd like to edit the default behavior, refer to the reference implementations in the SDK source code for examples.

Event dispatcher

You can optionally change how the SDK sends events to Optimizely by providing an event dispatcher method. You should provide your own event dispatcher if you have networking requirements that aren't met by our default dispatcher.

The event dispatching function takes an event object with three properties: httpVerb, url, and params, all of which are built out for you in EventBuilder. A POST request should be sent to url with params in the body (be sure to stringify it to JSON) and {content-type: 'application/json'} in the headers.

Our Python SDK includes a basic synchronous event dispatcher that can be seen here. If you are using the SDK in a production environment you should provide your own event dispatcher using the networking library of your choice. Examples are available in the code samples section.

Our Ruby SDK includes a basic synchronous event dispatcher that can be seen here. If you are using the SDK in a production environment you should provide your own event dispatcher using the networking library of your choice. Examples are available in the code samples section.

By default, our Node SDK uses a basic asynchronous event dispatcher that can be seen here. If you provide a custom event dispatcher we will expect it to return an ES6 type Promise.

Our PHP SDK includes a basic synchronous event dispatcher. If you are using the SDK in a production environment you should provide your own event dispatcher using the networking library of your choice.

We also provide another event dispatcher which calls curl externally to POST data to Optimizely. This event dispatcher has better performance as the process forks and executes in the background, thereby not blocking your application. You need to make sure that you have curl installed on your server to be able to use it. The code for this curl based event dispatcher can be seen here.

By default, our JavaScript SDK uses a basic asynchronous event dispatcher that can be seen here.

By default, our C# SDK uses an asynchronous event dispatcher as shown on the right.

We provide an asynchronous event dispatcher, optimizely-sdk-httpclient, that requires org.apache.httpcomponents:httpclient:4.5.2 and is parameterized by in-memory queue size and number of dispatch worker threads.

To use your own event dispatcher, implement the dispatchEvent method in our EventHandler interface. dispatchEvent takes in a LogEvent object containing all of the information needed to dispatch an event to Optimizely's backend. Events should be sent to LogEvent.getEndpointUrl as a POST request with LogEvent.getBody as the payload and content-type: "application/json" as the header.

The included event handler implementation includes queueing and flushing so will work even if an app is offline. It uses an IntentService to queue up events to dispatch. If they fail to send they are stored in a sqlite database and scheduled to be dispatched later. If you would like to have events flushed when wifi is available or after reboot or reinstall using the default event handler, you need to augment your AndroidManifest.xml to include the intent filter declarations.

Example Code

EventHandler eventHandler = YourEventHandler.getInstance();
// Create an Optimizely client with your own event handler
Optimizely optimizelyClient = Optimizely.builder(datafile).
withEventHandler(eventHandler).
build(getApplicationContext());
// With the new Android O differences, you need to register the service for the intent filter you desire in code instead of in the manifest.
EventRescheduler eventRescheduler = new EventRescheduler();
getApplicationContext().registerReceiver(eventRescheduler, new IntentFilter(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION));

Logger

The logger logs information about your experiments to help you with debugging.

In the Python SDK, logging functionality is not enabled by default. To improve your experience setting up the SDK and configuring your production environment, we recommend that you pass in a logger for your Optimizely client.

You can use our SimpleLogger implementation out of the box, available here.

In the Ruby SDK, logging functionality is not enabled by default. To improve your experience setting up the SDK and configuring your production environment, we recommend that you pass in a logger for your Optimizely client.

You can use our SimpleLogger implementation out of the box, available here.

In the Node SDK, logging functionality is not enabled by default. To improve your experience setting up the SDK and configuring your production environment, we recommend that you pass in a logger for your Optimizely client.

By default, the JavaScript SDK uses our Logger implementation, available here. You can also implement your own logger and pass it into the builder when creating an Optimizely instance.

It's also possible to configure the log level threshold of the default logger without implementing your own logger. You can do so by passing the constructor an integer logLevel. Possible levels are enumerated here.

We are working on a default logger for our PHP SDK. Stay posted for updates.

In the Java SDK, logging functionality is not enabled by default. To improve your experience setting up the SDK and configuring your production environment, we recommend that you include a SLF4J implementation in your dependencies.

For the Android SDK, you can use our Android SLF4J logger. Logging verbosity can be controlled via the android-logger.properties file. This file can be placed in src/main/resources. You can also include a copy in src/release/resources with different settings for the build you release to the Play Store.

For the Objective-C SDK, you can use our default logger and configure OptimizelyLogLevelInfo. You can initialize it with any log level and pass it in when creating an Optimizely instance. You can also implement your own logger and pass it in the builder when creating an Optimizely instance.

For the Swift SDK, you can use our default logger and configure OptimizelyLogLevel.Info. You can initialize it with any log level and pass it in when creating an Optimizely instance. You can also implement your own logger and pass it in when creating an Optimizely instance.

The log levels vary slightly between SDKs but generally fall in the following buckets:

Log Levels

CRITICAL

Events that cause the app to crash are logged.

ERROR

Events that prevent experiments from functioning correctly (e.g., invalid datafile in initialization, invalid experiment keys or event keys) are logged. The user can take action to correct.

WARNING

Events that don't prevent experiments from functioning correctly, but can have unexpected outcomes (e.g. future API deprecation, logger or error handler are not set properly, nil values from getters) are logged.

DEBUG

Any information related to errors that can help us debug the issue (e.g., experiment is not running, user is not in experiment, the bucket value generated by our helper method) are logged.

INFO

Events of significance (e.g, activate started, activate succeeded, tracking started, tracking succeeded) are logged. This is helpful in showing the lifecycle of an API call

Error handler

You can provide your own custom error handler logic to standardize across your production environment. This error handler will be called in the following situations:

Unknown experiment key referenced

Unknown event key referenced

If the error handler isn’t overridden, a no-op error handler is used by default.

You can provide your own custom error handler logic by replacing the errorHandler property in the OPTLYManagerBuilder object during initialization. The error handler you create must conform to the OPTLYErrorHandler protocol. Overriding this module will allow you to standardize error and exception handling across your application.

You can provide your own custom error handler logic by replacing the errorHandler property in the OPTLYManagerBuilder object during initialization. The error handler you create must conform to the OPTLYErrorHandler protocol. Overriding this module, will allow you to standardize error and exception handling across your application.

User Profile Service

Use a user profile service to persist information about your users and ensure variation assignments are sticky. For example, if you are working on a backend website, you can create an implementation that reads and saves user profiles from a Redis or memcached store.

Implementing a user profile service is optional and is only necessary if you want to keep variation assignments sticky even when experiment conditions are changed while it is running (e.g. audiences, attributes, variation pausing, traffic distribution). For more information on user profiles and how they used by the SDK, see this Optiverse article.

To use, you must provide an implementation of the UserProfileService that exposes two functions with the following signatures below.

The SDK uses the user profile service you provide to override Optimizely's default bucketing behavior in cases when an experiment assignment has been saved.

When implementing your own UserProfileService we recommend loading the user profiles into the user profile service on initialization and avoiding performing expensive, blocking lookups on the lookup function to minimize the performance impact of incorporating the UserProfileService.

Unlike our client-side SDKs, our Java SDK does not currently manage user state. If you would like to persist data about your users between requests (e.g. unique identifiers, user attributes, bucketing decisions) you can store this state in the client or in your own custom datastore. The UserProfileService is available in Optimizely Java SDK 1.7.0.

To use, you must provide an implementation of the UserProfileService that exposes two functions with the following signatures below.

The SDK uses the user profile service you provide to override Optimizely's default bucketing behavior in cases when an experiment assignment has been saved.

When implementing your own UserProfileService we recommend loading the user profiles into the user profile service on initialization and avoiding performing expensive, blocking lookups on the lookup function to minimize the performance impact of incorporating the UserProfileService.

The SDK uses the user profile service you provide to override Optimizely's default bucketing behavior in cases when an experiment assignment has been saved.

When implementing your own UserProfileService we recommend loading the user profiles into the user profile service on initialization and avoiding performing expensive, blocking lookups on the Lookup function to minimize the performance impact of incorporating the UserProfileService.

To use, you must provide an implementation of the UserProfileService that exposes two functions, lookup and save, with the following signatures.

The SDK uses the user profile service you provide to override Optimizely's default bucketing behavior in cases when an experiment assignment has been saved.

When implementing your own UserProfileService we recommend loading the user profiles into the user profile service on initialization and avoiding performing expensive, blocking lookups on the lookup function to minimize the performance impact of incorporating the UserProfileService.

To use, you must provide an implementation of the UserProfileService that exposes two functions with the following signatures below.

The SDK uses the user profile service you provide to override Optimizely's default bucketing behavior in cases when an experiment assignment has been saved.

When implementing your own UserProfileService we recommend loading the user profiles into the user profile service on initialization and avoiding performing expensive, blocking lookups on the lookup function to minimize the performance impact of incorporating the UserProfileService.

To use, you must provide an implementation of the UserProfileService that exposes two functions with the following signatures below.

The SDK uses the user profile service you provide to override Optimizely's default bucketing behavior in cases when an experiment assignment has been saved.

When implementing your own UserProfileService we recommend loading the user profiles into the user profile service on initialization and avoiding performing expensive, blocking lookups on the lookup function to minimize the performance impact of incorporating the UserProfileService.

The UserProfileService interface implementation can be used for persisting information about your users in a local profile. You can provide your own UserProfileService implementation if you’d like to change what user information is persisted or how data is stored on the device. The code at right shows how to include your own user profile implementation when constructing an Optimizely client.

Our default UserProfileService implementation, DefaultUserProfileService, stores data on which experiments and variations have historically been activated for a user. The Optimizely client uses the supplied user profile to ensure that experiment bucketing is sticky, i.e. the user will always be exposed to the same variation on subsequent sessions in the app, even in the case where the experiment is paused or traffic allocation is changed. We plan to expand our implementation in future to include more information about users, including user IDs and attributes.

The UserProfileService interface contains the following methods:

save: Takes a userId, experimentId, and variationId and saves this information on the device.

lookup: Takes a userId and experimentId, and returns the variationId that was recorded previously if it exists.

remove: Takes a userId and experimentId, and deletes the mapping to the variation if it exists.

The OPTLYUserProfileService class is used for persisting information about users. You can provide your own OPTLYUserProfileService implementation if you want to change what user information is saved or how data is stored on the device. The code at right shows how to include your own user profile implementation when constructing an Optimizely manager.

The default OPTLYUserProfileService implementation, OptimizelySDKUserProfileService, stores data on which experiments and variations a user has been exposed to. The Optimizely client uses the supplied user profile service to ensure that experiment bucketing is sticky. We plan to expand our implementation in the future to include more information about users, including attributes.

The OPTLYUserProfileService class is used for persisting information about users. You can provide your own OPTLYUserProfileService implementation if you want to change what user information is saved or how data is stored on the device. The code at right shows how to include your own user profile implementation when constructing an Optimizely manager.

The default OPTLYUserProfileService implementation, OptimizelySDKUserProfileService, stores data on which experiments and variations a user has been exposed to. The Optimizely client uses the supplied user profile service to ensure that experiment bucketing is sticky. We plan to expand our implementation in the future to include more information about users, including user IDs and attributes.

Datafile Handler

The default datafile handler is only available in mobile sdks.

On some SDKs, you can use a default datafile handler or provide an override. The datafile handler handles how often the datafile is polled for changes. If a modified datafile is found, it is cached for the next startup of the application. The datafile cache is also available to you. All of these reside in the datafile-handler module on Android.
The included event handler implementation includes queueing and flushing so will work even if an app is offline. It uses an IntentService to queue up events to dispatch. If they fail to send they are stored in a sqlite database and scheduled to be dispatched later. If you would like to have events flushed when wifi is available or after reboot or reinstall using the default event handler, you need to augment your AndroidManifest.xml to include the intent filter declarations.

IP anonymization

In some countries, you may be required to remove the last block of an IP address to protect the identity of your visitors. Optimizely allows you to easily remove the last block of your visitors' IP address before we store results data. This feature is currently available only for iOS, tvOS, and Android projects. To enable IP anonymization, see our Optiverse article.

Integrations

You can add Optimizely experiment information to third-party analytics platforms using Notification Listeners. Measure the impact of your experiments by segmenting your analytics reports using Optimizely experiment and variation keys or IDs.

Notification Listeners also allow for the flexibility to implement an integration with a platform that is not listed here.

Experiment and Variation Identifiers

Experiment keys are unique within an Optimizely project, and variation keys are unique within an experiment. However, experiment and variation keys are not guaranteed to be universally unique since they are user generated. This may become a problem in your analytics reports if you use the same key in multiple Optimizely projects or rename your keys.

For human friendly strings when absolute uniqueness is not required, use keys. If you need to uniquely identify experiments and variations in a way that will not change when you rename keys, you can use the automatically generated IDs.

Keys can be accessed on Experiment and Variation objects using getKey(), and IDs can be accessed using getId().

Keys and IDs can be accessed on OPTLYExperiment objects using experimentKey and experimentId respectively, and likewise keys and IDs can be accessed on OPTLYVariation objects using variationKey and variationId.

Keys and IDs can be accessed on OPTLYExperiment objects using experimentKey and experimentId respectively, and likewise keys and IDs can be accessed on OPTLYVariation objects using variationKey and variationId.

Keys can be accessed on Experiment and Variation objects using getKey(), and IDs can be accessed using getId().

Amplitude

Amplitude features two types of properties that can be used to enrich and segment their reports with Optimizely experiment information: event properties and user properties.

User properties describe a user across all logged events. After you set a user property, Amplitude applies it in their backend to subsequent recorded events until someone changes the property.

Event properties describe a single event, so they must be added to each logged event. The Amplitude SDK and backend do not automatically add event properties to future events. For this reason, user properties are simpler to work with than event properties are.

The example code demonstrates the ActivateNotificationListerner.onActivate callback. In the callback, the code shows Amplitude's Identify interface setting a new user property, which includes the experiment key and variation key. The user property should immediately propagate to the Amplitude backend and be visible in their dashboard. To compare the performances of variations in an experiment, create a new segment for each variation.

Optionally, you can log an impression event in the callback to signify that the Optimizely experiment was activated for the current user. You can later use this event (or another event you may already be tracking) to calculate a conversion rate.

Compare results

When comparing numbers between Optimizely and Amplitude results, remember to apply a date filter in Amplitude that corresponds with the dates your Optimizely experiment was running. Note: user properties remain set in Amplitude, regardless of whether your Optimizely experiment is running.

Alternative solution for Optimizely Enterprise accounts

If you have an Optimizely Enterprise plan, you alternatively can use Amplitude's Behavioral Cohort Analysis, which can segment on the impression event tracked in the example code.

Optimizely does not have a suggested solution for integrating Amplitude with the SDK.

Optimizely does not have a suggested solution for integrating Amplitude with the SDK.

Amplitude features two types of properties that can be used to enrich and segment their reports with Optimizely experiment information: event properties and user properties.

User properties describe a user across all logged events. After you set a user property, Amplitude applies it in their backend to subsequent recorded events until someone changes the property.

Event properties describe a single event, so they must be added to each logged event. The Amplitude SDK and backend do not automatically add event properties to future events. For this reason, user properties are simpler to work with than event properties are.

The example code demonstrates an onActivate callback. In the callback, the code shows Amplitude's Identify interface setting a new user property, which includes the experiment key and variation key. The user property should immediately propagate to the Amplitude backend and be visible in its dashboard. To compare the performances of variations in an experiment, create a new segment for each variation.

Optionally, you can log an impression event in the callback to signify that the Optimizely experiment was activated for the current user. You can later use this event (or another event you may already be tracking) to calculate a conversion rate.

Compare results

When comparing numbers between Optimizely and Amplitude results, remember to apply a date filter in Amplitude that corresponds with the dates your Optimizely experiment was running. Note: user properties remain set in Amplitude, regardless of whether your Optimizely experiment is running.

Alternative solution for Optimizely Enterprise accounts

If you have an Optimizely Enterprise plan, you alternatively can use Amplitude's Behavioral Cohort Analysis, which can segment on the impression event tracked in the example code.

Optimizely does not have a suggested solution for integrating Amplitude with the SDK.

Amplitude features two types of properties that can be used to enrich and segment their reports with Optimizely experiment information: event properties and user properties.

User properties describe a user across all logged events. After you set a user property, Amplitude applies it in their backend to subsequent recorded events until someone changes the property.

Event properties describe a single event, so they must be added to each logged event. The Amplitude SDK and backend do not automatically add event properties to future events. For this reason, user properties are simpler to work with than event properties are.

The example code demonstrates an activate callback. In the callback, the code shows Amplitude's AMPIdentify interface setting a new user property, which includes the experiment key and variation key. The user property should immediately propagate to the Amplitude backend and be visible in its dashboard. To compare the performances of variations in an experiment, create a new segment for each variation.

Optionally, you can log an impression event in the callback to signify that the Optimizely experiment was activated for the current user. You can later use this event (or another event you may already be tracking) to calculate a conversion rate.

Compare Results

When comparing numbers between Optimizely and Amplitude results, remember to apply a date filter in Amplitude that corresponds with the dates your Optimizely experiment was running. Note: user properties remain set in Amplitude, regardless of whether your Optimizely experiment is running.

Alternative Solution

If you have an Optimizely Enterprise plan, you alternatively can use Amplitude's Behavioral Cohort Analysis, which can segment on the impression event tracked in the example code.

Optimizely does not have a suggested solution for integrating Amplitude with the SDK.

Optimizely does not have a suggested solution for integrating Amplitude with the SDK.

Optimizely does not have a suggested solution for integrating Amplitude with the SDK.

Amplitude features two types of properties that can be used to enrich and segment their reports with Optimizely experiment information: event properties and user properties.

User properties describe a user across all logged events. After you set a user property, Amplitude applies it in their backend to subsequent recorded events until someone changes the property.

Event properties describe a single event, so they must be added to each logged event. The Amplitude SDK and backend do not automatically add event properties to future events. For this reason, user properties are simpler to work with than event properties are.

The example code demonstrates an activate callback. In the callback, the code shows Amplitude's AMPIdentify interface setting a new user property, which includes the experiment key and variation key. The user property should immediately propagate to the Amplitude backend and be visible in their dashboard. To compare the performances of variations in an experiment, create a new segment for each variation.

Optionally, you can log an impression event in the callback to signify that the Optimizely experiment was activated for the current user. You can later use this event (or another event you may already be tracking) to calculate a conversion rate.

Compare results

When comparing numbers between Optimizely and Amplitude results, remember to apply a date filter in Amplitude that corresponds with the dates your Optimizely experiment was running. Note: user properties remain set in Amplitude, regardless of whether your Optimizely experiment is running.

Alternative solution for Optimizely Enterprise accounts

If you have an Optimizely Enterprise plan, you alternatively can use Amplitude's Behavioral Cohort Analysis, which can segment on the impression event tracked in the example code.

Google Analytics

With Google Analytics, you can use events to track Optimizely experiment activations and build segments for each variation in an experiment. For more details on alternative solutions, see below.

In the example code, the ActivateNotificationListern.onActivate callback is tracking experiment activations. When the callback is called, the code creates and sends an event containing the experiment key and variation key. Because this event should not affect the bounce rate, it is a non-interaction event.

The example code finishes by adding the listener to the Optimizely SDK.

The next step is to build segments for each variation of your experiment. In the segment-builder user interface, look under Advanced and add a new Condition. Select to filter Users by Event Action and Event Label, enter a name for the segment, and save.

Finally, build a report of metrics you are interested in.

Compare results

When comparing numbers between Optimizely and Google Analytics results, remember to apply a date filter in Google Analytics to correspond with the dates your Optimizely experiment was running.

Alternative solution

Google offers custom dimensions as another way to "include non-standard data in your reports." Custom dimensions are the replacement for custom variables from older versions of Google Analytics.

Optimizely's suggested integration does not use custom dimensions because free Google Analytics accounts are limited to 20 indices (or slots), and 360 accounts are limited to 200 indices. Because a custom dimension cannot store a value that exceeds 150 bytes, you would need to dedicate an index for each Optimizely experiment. Google recommends against reusing indices; thus, the number of available indicies is the upper limit of Optimizely experiments that you can use custom dimensions to track and segment.

Optimizely does not have a suggested solution for integrating Google Analytics with the SDK.

Optimizely does not have a suggested solution for integrating Google Analytics with the SDK.

With Google Analytics, you can use events to track Optimizely experiment activations and build segments for each variation in an experiment. For more details on alternative solutions, see below.

In the example code, we add a onActivate callback to track experiment activations. When the callback is called, we create and send an event containing the experiment key and variation key. Since this should not affect the bounce rate, we mark it as a non-interaction event.

The next step is to build segments for each variation of your experiment. In the segment-builder user interface, look under Advanced and add a new Condition. Select to filter Users by Event Action and Event Label, enter a name for the segment, and save.

Finally, build a report of metrics you are interested in.

Compare results

When comparing numbers between Optimizely and Google Analytics results, remember to apply a date filter in Google Analytics to correspond with the dates your Optimizely experiment was running.

Alternative solution

Google offers custom dimensions as another way to "include non-standard data in your reports." Custom dimensions are the replacement for custom variables from older versions of Google Analytics.

Optimizely's suggested integration does not use custom dimensions because free Google Analytics accounts are limited to 20 indices (or slots), and 360 accounts are limited to 200 indices. Because a custom dimension cannot store a value that exceeds 150 bytes, you would need to dedicate an index for each Optimizely experiment. Google recommends against reusing indices; thus, the number of available indicies is the upper limit of Optimizely experiments that you can use custom dimensions to track and segment.

Optimizely does not have a suggested solution for integrating Google Analytics with the SDK.

With Google Analytics, you can use events to track Optimizely experiment activations and build segments for each variation in an experiment. For more details on alternative solutions, see below.

The example code shows how to add an activate listener block to the notification center. When the callback is called, the code creates and sends an event containing the experiment key and variation key.

The next step is to build segments for each variation of your experiment. In the segment-builder user interface, look under Advanced and add a new Condition. Select to filter Users by Event Action and Event Label, enter a name for the segment, and save.

Finally, build a report of metrics you are interested in.

Compare results

When comparing numbers between Optimizely and Google Analytics results, remember to apply a date filter in Google Analytics to correspond with the dates your Optimizely experiment was running.

Alternative solution

Google offers custom dimensions as another way to "include non-standard data in your reports." Custom dimensions are the replacement for custom variables from older versions of Google Analytics.

Optimizely's suggested integration does not use custom dimensions because free Google Analytics accounts are limited to 20 indices (or slots), and 360 accounts are limited to 200 indices. Because a custom dimension cannot store a value that exceeds 150 bytes, you would need to dedicate an index for each Optimizely experiment. Google recommends against reusing indices; thus, the number of available indicies is the upper limit of Optimizely experiments that you can use custom dimensions to track and segment.

Optimizely does not have a suggested solution for integrating Google Analytics with the SDK.

Optimizely does not have a suggested solution for integrating Google Analytics with the SDK.

Optimizely does not have a suggested solution for integrating Google Analytics with the SDK.

With Google Analytics, you can use events to track Optimizely experiment activations and build segments for each variation in an experiment. For more details on alternative solutions, see below.

The example code shows how to add an activate listener closure to the notification center. When the callback is called, the code creates and sends an event containing the experiment key and variation key.

The next step is to build segments for each variation of your experiment. In the segment-builder user interface, look under Advanced and add a new Condition. Select to filter Users by Event Action and Event Label, enter a name for the segment, and save.

Finally, build a report of metrics you are interested in.

Compare results

When comparing numbers between Optimizely and Google Analytics results, remember to apply a date filter in Google Analytics to correspond with the dates your Optimizely experiment was running.

Alternative solution

Google offers custom dimensions as another way to "include non-standard data in your reports." Custom dimensions are the replacement for custom variables from older versions of Google Analytics.

Optimizely's suggested integration does not use custom dimensions because free Google Analytics accounts are limited to 20 indices (or slots), and 360 accounts are limited to 200 indices. Because a custom dimension cannot store a value that exceeds 150 bytes, you would need to dedicate an index for each Optimizely experiment. Google recommends against reusing indices; thus, the number of available indicies is the upper limit of Optimizely experiments that you can use custom dimensions to track and segment.

Localytics

Localytics has a couple of ways that can be used to capture Optimizely experiment information. This example demonstrates the use of Custom Events with Attributes in this suggested integration. For more details on alternative solutions, see below.

This example involves two parts:

Add a TrackNotificationListener listener for onEvent to wrap Localytics.tagEvent()

Add OptimizelyClient.track() to track conversions

Instead of calling Localytics.tagEvent() directly, wrap the calls with OptimizelyClient.track() to include bucketing information as event attributes.

The example code demonstrates how to add a track event listener. Each OptimizelyClient.track() event tracking retrieves a mapping of experiment key to variation key from UserProfile, which records bucketing decisions. Next, the code calls Localytics.tagEvent() and includes the bucketing map among the attributes.

The last step is to add OptimizelyClient.track() to track event conversions.

Compare Results

When comparing numbers between Optimizely and Localytics results, remember to apply a date filter in Localytics that corresponds with the dates your Optimizely experiment was running.

Consistent User Identity

Maintaining a consistent user identity across multiple sessions and devices can help ensure proper reporting. Localytics provides some guidelines for their platform.

Optimizely recommends using the same user ID with the following methods:

optimizelyClient.activate()

Localytics.setCustomerId()

Alternative Solution

Another solution is to set Localytics' Custom Dimensions using an ActivateNotificationListener. Custom dimensions can be used to segment users without needing to wrap Localytics.tagEvent(), but they require configuration in the Localytics dashboard for each Optimizely experiment.

Optimizely does not have a suggested solution for integrating Localytics with the SDK.

Optimizely does not have a suggested solution for integrating Localytics with the SDK.

Optimizely does not have a suggested solution for integrating Localytics with the SDK.

Optimizely does not have a suggested solution for integrating Localytics with the SDK.

Localytics has a couple of ways that can be used to capture Optimizely experiment information. This example demonstrates the use of Custom Events with Attributes in this suggested integration. For more details on alternative solutions, see below.

This example involves two parts:

Add a track event listener to wrap [Localytics tagEvent]

Add [optimizely track] to track conversions

Instead of calling [Localytics tagEvent] directly, wrap the calls with [optimizely track] to include bucketing information as event attributes.

The example code demonstrates how to add the track notification listener. Each [optimizely track] event tracking adds a mapping of experiment key to variation key to the event attributes and passes the mapping to [Localytics tagEvent].

The last step is to add [optimizely track] to track event conversions.

Compare Results

When comparing numbers between Optimizely and Localytics results, remember to apply a date filter in Localytics that corresponds with the dates your Optimizely experiment was running.

Consistent User Identity

Maintaining a consistent user identity across multiple sessions and devices can help ensure proper reporting. Localytics provides some guidelines for their platform.

Optimizely recommends using the same user ID with the following methods:

[optimizely activate]

[Localytics setCustomerId]

Alternative Solution

Another solution is to set Localytics' Custom Dimensions using an activate notification listener. Custom dimensions can be used to segment users without needing to wrap [Localytics tagEvent], but they require configuration in the Localytics dashboard for each Optimizely experiment.

Optimizely does not have a suggested solution for integrating Localytics with the SDK.

Optimizely does not have a suggested solution for integrating Localytics with the SDK.

Optimizely does not have a suggested solution for integrating Localytics with the SDK.

Localytics has a couple of ways that can be used to capture Optimizely experiment information. This example demonstrates the use of Custom Events with Attributes in this suggested integration. For more details on alternative solutions, see below.

This example involves two parts:

Add a track notification listener to wrap Localytics.tagEvent()

Add optimizely.track() to track conversions

Instead of calling Localytics.tagEvent() directly, wrap the calls with optimizely.track() to include bucketing information as event attributes.

The example code demonstrates how to add a track notification listener. Each time optimizely.track() event tracking adds a mapping of experiment key to variation key to the event attributes and pass the mapping to Localytics.tagEvent().

The last step is to add optimizely.track() to track event conversions.

Compare Results

When comparing numbers between Optimizely and Localytics results, remember to apply a date filter in Localytics that corresponds with the dates your Optimizely experiment was running.

Consistent User Identity

Maintaining a consistent user identity across multiple sessions and devices can help ensure proper reporting. Localytics provides some guidelines for their platform.

Optimizely recommends using the same user ID with the following methods:

optimizely.activate()

Localytics.setCustomerId()

Alternative Solution

Another solution is to set Localytics' Custom Dimensions using an activate notification listener. Custom dimensions can be used to segment users without needing to wrap Localytics.tagEvent() but requires configuration in the Localytics dashboard for each Optimizely experiment.

Mixpanel supports two types of properties that can be used to segment data in their reports: Super Properties and People Properties. Each kind of property captures a slightly different aspect of the events and users they describe and differ in the ways they can be used for reporting. For maximum flexibility in reporting, we will use both super properties and people properties like Mixpanel suggests.

In the example code, we add an activate listener block callback. In the callback, we first set the super property and then the people property with Mixpanel.

Next in the callback we can optionally log an impression event that signifies that the Optimizely experiment was activated for the current user. You can later use this event or another event you may already be tracking to calculate a conversion rate.

Compare Results

When comparing numbers between Optimizely and Mixpanel results, remember to apply a date filter in Mixpanel to correspond with the dates your Optimizely experiment was running. People properties and super properties will remain set in Mixpanel even after your Optimizely experiment has stopped running.

Consistent User Identity

Maintaining a consistent user identity across multiple sessions and devices can help ensure proper reporting. Mixpanel provides some guidelines for their platform.

Mixpanel supports two types of properties that can be used to segment data in their reports: Super Properties and People Properties. Each kind of property captures a slightly different aspect of the events and users they describe and differ in the ways they can be used for reporting. For maximum flexibility in reporting, we will use both super properties and people properties like Mixpanel suggests.

In the example code, we add an activate listener closure callback. In the callback, we first set the super property and then the people property with Mixpanel.

Next in the callback we can optionally log an impression event that signifies that the Optimizely experiment was activated for the current user. You can later use this event or another event you may already be tracking to calculate a conversion rate.

Compare Results

When comparing numbers between Optimizely and Mixpanel results, remember to apply a date filter in Mixpanel to correspond with the dates your Optimizely experiment was running. People properties and super properties will remain set in Mixpanel even after your Optimizely experiment has stopped running.

Consistent User Identity

Maintaining a consistent user identity across multiple sessions and devices can help ensure proper reporting. Mixpanel provides some guidelines for their platform.

Mixpanel supports two types of properties that can be used to segment data in their reports: Super Properties and People Properties. Each kind of property captures a slightly different aspect of the events and users they describe and differ in the ways they can be used for reporting. For maximum flexibility in reporting, we will use both super properties and people properties like Mixpanel suggests.

In the example code, we add a notification listener using ActivateNotificationListener abstract class callback (we could also use the ActivateNotificationListenerInterface or a lambda). In the callback, we first set the super property and then the people property with Mixpanel.

Next in the callback we can optionally log an impression event that signifies that the Optimizely experiment was activated for the current user. You can later use this event or another event you may already be tracking to calculate a conversion rate.

At the end of the example code, we add our listener to the Optimizely SDK.

Compare Results

When comparing numbers between Optimizely and Mixpanel results, remember to apply a date filter in Mixpanel to correspond with the dates your Optimizely experiment was running. People properties and super properties will remain set in Mixpanel even after your Optimizely experiment has stopped running.

Consistent User Identity

Maintaining a consistent user identity across multiple sessions and devices can help ensure proper reporting. Mixpanel provides some guidelines for their platform.