Integrate Mixpanel with Optimizely Web Experimentation

Loading...·13 min read

Mixpanel is a product analytics platform built around event-based tracking and behavioral cohorts. Integrating Mixpanel with Optimizely Web Experimentation routes experiment decision data into Mixpanel as super properties and events, enabling you to build funnels segmented by variation, run retention analyses across control and treatment groups, and use Mixpanel cohorts to target Optimizely experiments.

This guide covers two integration methods: the built-in Mixpanel integration (a toggle in the Optimizely dashboard) and a custom JSON integration using the track_layer_decision callback for teams that need control over data formatting and property naming. It also covers Mixpanel Cohort Sync via Optimizely Data Platform (ODP), which lets you push Mixpanel cohorts into Optimizely for experiment targeting.

How the Integration Works

When Optimizely buckets a visitor into an experiment or personalization campaign, it fires a decision callback. The integration captures this callback and sends data to Mixpanel in two forms: a super property registered on the Mixpanel client (which automatically attaches to all subsequent mixpanel.track() calls), and a discrete "Experiment Decided" event with campaign metadata. Mixpanel then associates the experiment context with every event the user fires after being bucketed.

flowchart LR
    A[Visitor lands on page] --> B[Optimizely makes bucketing decision]
    B --> C[Decision callback fires]
    C --> D["mixpanel.register() sets super property"]
    C --> E["mixpanel.track('Experiment Decided')"]
    D --> F[Super property attaches to all future events]
    E --> G[Mixpanel Event Stream]
    F --> H[Segment Funnels by variation]
    G --> H

Super properties are Mixpanel's mechanism for attaching session-level context to every event. Once mixpanel.register() is called with the experiment property, every subsequent mixpanel.track() call within that session automatically includes it — no additional instrumentation is required.

Native Integration Property Format

The built-in integration sets a super property using this format:

<experiment_name> (<experiment_id>): <variation_name> (<variation_id>)

The decision event is named:

Experiment Decided: <experiment_name> (<experiment_id>)

For personalization campaigns, the format follows the same pattern with campaign and experience names:

<campaign_name> (<campaign_id>): <experience_name> (<experience_id>)

With a corresponding decision event:

Campaign Decided: <campaign_name> (<campaign_id>)

Decision Event Properties

Property

Type

Description

campaign_id

string

The campaign (layer) ID in Optimizely

experiment_id

string

The experiment ID within the campaign

experiment_name

string

Human-readable experiment name

variation_id

string

The assigned variation ID

variation_name

string

Human-readable variation name

is_holdback

boolean

Whether the visitor is in the holdback group

Prerequisites

Before starting the integration, confirm the following:

  • Mixpanel JS SDK is installed and initialized on your site. The Mixpanel SDK must load before the Optimizely Web Experimentation snippet fires its first decision. If Mixpanel loads after Optimizely, the initial bucketing event is lost.

  • Optimizely Web Experimentation snippet is deployed on the same pages.

  • Admin access to both the Mixpanel project (for verifying data) and the Optimizely project (Settings > Integrations).

  • Mixpanel project token is configured in your Mixpanel SDK initialization.

Load Order

The Mixpanel SDK must be available in window.mixpanel before Optimizely makes its first bucketing decision. Load Mixpanel in the <head> tag before the Optimizely snippet:

<head>
  <!-- 1. Mixpanel SDK (must load first) -->
  <script src="https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js"></script>
  <script>
    mixpanel.init("YOUR_PROJECT_TOKEN");
  </script>

  <!-- 2. Optimizely Web snippet (loads second) -->
  <script src="https://cdn.optimizely.com/js/YOUR_PROJECT_ID.js"></script>
</head>

If you use a tag manager, ensure the Mixpanel tag fires with a higher priority than the Optimizely snippet. Verify load order with the browser console before relying on this in production.

Method 1: Built-in Mixpanel Integration

Optimizely includes a native Mixpanel integration in its integrations catalog. This is the fastest setup option and requires no custom code.

Step 1: Enable the Integration in Optimizely

  1. In your Optimizely project, go to Settings > Integrations.

  2. Find Mixpanel in the integrations list.

  3. Toggle the integration to Enabled.

  4. Optionally check Enable for all new experiments to apply the integration automatically to future experiments.

Step 2: Enable per Experiment

For experiments created before you enabled the integration:

  1. Open the experiment in the Optimizely editor.

  2. Go to Manage Campaign > Integrations.

  3. Enable the Mixpanel integration for this experiment.

  4. Save the experiment.

What the Built-in Integration Sends

The built-in integration automatically:

  • Calls mixpanel.register() to set a super property on the visitor's Mixpanel session in the format <experiment_name> (<experiment_id>): <variation_name> (<variation_id>).

  • Sends a decision event (Experiment Decided: <experiment_name> (<experiment_id>)) to the Mixpanel event stream.

Because the experiment context is stored as a super property rather than a user profile property, it attaches automatically to every subsequent mixpanel.track() call. This means you can segment any Mixpanel event by experiment variation without modifying your existing tracking code.

Limitations of the Built-in Approach

  • You cannot customize the super property name or format.

  • The property name includes numeric IDs alongside human-readable names, which can make Mixpanel reports verbose.

  • You cannot add custom event properties beyond what Optimizely's integration provides.

  • The integration does not call mixpanel.people.set(), so experiment data does not appear on user profiles in Mixpanel People — only in session super properties.

  • If your site uses a non-standard Mixpanel initialization (e.g., named instances), the built-in integration may not resolve the correct mixpanel object.

If you need cleaner property names, user profile data, or more control over event structure, use the custom JSON integration described in Method 2.

Method 2: Custom JSON Integration

The custom JSON integration gives you full control over what data is sent to Mixpanel and how it is structured. This approach uses Optimizely's Custom Analytics Integration (JSON plugin) with the track_layer_decision callback to call the Mixpanel SDK directly.

Step 1: Create the JSON Integration

  1. In your Optimizely project, go to Settings > Integrations.

  2. Click Create Analytics Integration > Using JSON.

  3. Paste the following configuration:

{
  "plugin_type": "analytics_integration",
  "name": "Mixpanel (Custom)",
  "form_schema": [],
  "description": "Sends Optimizely experiment decisions to Mixpanel as super properties, user profile properties, and events",
  "options": {
    "track_layer_decision": "var state = window.optimizely && window.optimizely.get('state');\nvar decisionObj = null;\nvar expName = String(campaignId);\nvar varName = String(variationId);\n\nif (state) {\n  try {\n    decisionObj = state.getDecisionObject({ campaignId: campaignId });\n    if (decisionObj) {\n      expName = decisionObj.experiment || expName;\n      varName = decisionObj.variation || varName;\n    }\n  } catch (e) {}\n}\n\nvar propertyName = '[Optimizely] ' + expName;\nvar propertyValue = isHoldback ? 'holdback' : varName;\n\nif (window.mixpanel) {\n  var superProps = {};\n  superProps[propertyName] = propertyValue;\n  window.mixpanel.register(superProps);\n\n  window.mixpanel.people.set(propertyName, propertyValue);\n\n  window.mixpanel.track('Experiment Viewed', {\n    campaign_id: String(campaignId),\n    experiment_id: String(experimentId),\n    experiment_name: expName,\n    variation_id: String(variationId),\n    variation_name: propertyValue,\n    is_holdback: isHoldback\n  });\n}\n"
  }
}
  1. Click Save Integration.

What the Callback Does

The options.track_layer_decision callback fires each time Optimizely makes a bucketing decision. Here is a breakdown of the logic:

Retrieving human-readable names: The callback uses state.getDecisionObject({ campaignId }) to look up the experiment and variation names. If the state API is unavailable or the project has Mask descriptive names enabled, the callback falls back to numeric IDs.

var state = window.optimizely && window.optimizely.get('state');
var decisionObj = null;
var expName = String(campaignId);
var varName = String(variationId);

if (state) {
  try {
    decisionObj = state.getDecisionObject({ campaignId: campaignId });
    if (decisionObj) {
      expName = decisionObj.experiment || expName;
      varName = decisionObj.variation || varName;
    }
  } catch (e) {}
}

Setting the super property: mixpanel.register() attaches the experiment context to every subsequent mixpanel.track() call within the session. The property is keyed as [Optimizely] ExperimentName with the variation name as the value. Visitors in the holdback group are recorded as holdback.

var propertyName = '[Optimizely] ' + expName;
var propertyValue = isHoldback ? 'holdback' : varName;

var superProps = {};
superProps[propertyName] = propertyValue;
window.mixpanel.register(superProps);

Setting the user profile property: mixpanel.people.set() persists the experiment context on the Mixpanel user profile. Unlike super properties (session-scoped), people properties are permanent and visible in Mixpanel's People analytics.

window.mixpanel.people.set(propertyName, propertyValue);

Logging the event: The mixpanel.track() call sends a discrete "Experiment Viewed" event with full campaign metadata. This event enables time-based analysis, funnels that start at experiment entry, and filtering by specific campaigns.

window.mixpanel.track('Experiment Viewed', {
  campaign_id: String(campaignId),
  experiment_id: String(experimentId),
  experiment_name: expName,
  variation_id: String(variationId),
  variation_name: propertyValue,
  is_holdback: isHoldback
});

Step 2: Enable the Integration

After saving the JSON:

  1. Toggle the integration to Enabled in Settings > Integrations.

  2. Optionally check Enable for all new experiments.

  3. For existing experiments, go to each experiment's Manage Campaign > Integrations tab and enable the Mixpanel (Custom) integration.

Verifying the Integration

After enabling the integration, verify that data reaches Mixpanel correctly before analyzing results.

Console Verification

Open your browser's developer console on a page with an active experiment:

// Check if Mixpanel is loaded
console.log("Mixpanel loaded:", typeof window.mixpanel !== "undefined");

// Check current super properties (shows registered experiment properties)
if (window.mixpanel) {
  console.log("Mixpanel super properties:", window.mixpanel.get_property('[Optimizely] Your Experiment Name'));
}

// Check Optimizely experiment state
var state = window.optimizely && window.optimizely.get("state");
if (state) {
  var campaigns = state.getCampaignStates({ isActive: true });
  for (var id in campaigns) {
    var c = campaigns[id];
    console.log("Campaign " + id + ":", {
      experimentId: c.experiment && c.experiment.id,
      variationId: c.variation && c.variation.id,
      isHoldback: c.isInCampaignHoldback
    });
  }
}

Mixpanel Live View Verification

  1. In Mixpanel, go to Activity > Live View.

  2. Trigger a page visit on a page with an active experiment.

  3. Look for an "Experiment Viewed" event (custom integration) or an "Experiment Decided" event (native integration) in the live event stream.

  4. Click the event to expand its properties and verify experiment_name, variation_name, and campaign_id are present.

Mixpanel Insights Verification

To validate that data flows consistently across a broader sample:

  1. Go to Reports > Insights in Mixpanel.

  2. Select "Experiment Viewed" as the event.

  3. Group by experiment_name.

  4. Verify that your active experiments appear and that visitor counts are within expected ranges.

Analyzing Experiments in Mixpanel

Building Funnels by Variation

Funnels segmented by variation are one of the most direct ways to measure experiment impact on conversion sequences:

  1. Go to Reports > Funnels.

  2. Define your funnel steps (e.g., Page View > Product Detail > Add to Cart > Checkout > Purchase).

  3. In the Breakdown or Filter section, apply the super property [Optimizely] Your Experiment Name (custom integration) or the built-in super property to segment by variation.

  4. Compare step-by-step conversion rates between control and treatment variations.

Because Mixpanel super properties are session-scoped, the variation context automatically flows through all funnel steps without requiring additional tracking code on downstream pages.

Creating Cohorts by Variation

Create Mixpanel cohorts based on experiment participation for use in ongoing analysis:

  1. Go to Data Management > Cohorts > Create Cohort.

  2. Add a condition: event property on "Experiment Viewed" where variation_name equals Variation A.

  3. Alternatively, use the super property (which persists to people properties if using the custom integration): user property [Optimizely] Your Experiment Name equals Variation A.

  4. Save the cohort. It updates automatically as new visitors are bucketed.

You can use these cohorts as segments in any Mixpanel report, or push them back to Optimizely via ODP (see the Mixpanel Cohort Sync section below).

Retention Analysis by Variation

To measure whether a variation has a lasting effect on engagement:

  1. Go to Reports > Retention.

  2. Set the starting event to "Experiment Viewed" with a filter on experiment_name for your specific experiment.

  3. Set the return event to your key retention metric (e.g., "Session Start", "Purchase", or "Feature Used").

  4. Apply a breakdown by variation_name.

  5. Compare retention curves between variations over the desired time window.

Flows Analysis

Mixpanel's Flows report is useful for understanding whether a variation changes how users navigate through your product:

  1. Go to Reports > Flows.

  2. Set the starting point to the "Experiment Viewed" event for your experiment.

  3. Review the paths that users take after being bucketed into each variation.

  4. Filter by variation_name to compare navigation patterns between control and treatment.

This analysis is particularly useful for multivariate tests or personalization campaigns where the impact on navigation is as important as direct conversion.

Mixpanel Cohort Sync via ODP

Mixpanel does not have a native Optimizely cohort sync partner integration. However, you can push Mixpanel cohorts to Optimizely for experiment targeting using Optimizely Data Platform (ODP) with Mixpanel's Custom Webhook export. This enables targeting experiments at user segments defined by Mixpanel behavioral analysis.

flowchart LR
    A[Define behavioral cohort in Mixpanel] --> B[Configure Custom Webhook in Mixpanel]
    B --> C[Cohort membership sent to ODP via webhook]
    C --> D[ODP stores audience with mixpanel_ prefix]
    D --> E[Create Optimizely audience using ODP attribute]
    E --> F[Target experiment to Mixpanel cohort]

Setup Steps

In ODP:

  1. In Optimizely, go to Data Platform > App Directory.

  2. Find the Webhooks or Custom Integration app and generate a webhook endpoint URL and authentication token.

  3. Note the endpoint URL — you will configure this in Mixpanel.

In Mixpanel:

  1. Go to Data Management > Cohorts and select or create the cohort you want to sync.

  2. Click Export > Custom Webhook.

  3. Enter the ODP webhook endpoint URL and authentication headers.

  4. Configure the sync schedule (hourly, daily, or on cohort recalculation).

  5. Save the export configuration.

In Optimizely:

  1. After the first sync completes, ODP creates a custom audience attribute prefixed with mixpanel_ (e.g., mixpanel_power_users).

  2. In your Optimizely project, create a new audience and add a condition using the ODP attribute mixpanel_power_users.

  3. Apply this audience as a targeting condition for your experiment.

Use Cases for Cohort-Targeted Experiments

Mixpanel Cohort

Experiment Targeting Use Case

Users with 5+ sessions in the last 14 days

Test power-user features only for engaged visitors

Users who abandoned checkout in the last 30 days

Test checkout recovery flows on re-engaged visitors

Users who viewed the pricing page 3+ times

Test urgency elements on high-intent visitors

Users who have never completed onboarding

Test onboarding simplification on incomplete users

Gotchas

SDK Load Order

The Mixpanel SDK must be initialized before Optimizely makes its first decision. If the Optimizely snippet fires before window.mixpanel is available, the initial bucketing event is silently lost. There is no retry mechanism in the track_layer_decision callback.

To verify load order, add a temporary diagnostic check:

// Add temporarily to verify initialization order
console.log("Mixpanel available at DOMContentLoaded:", !!window.mixpanel);
console.log("Optimizely available at DOMContentLoaded:", !!window.optimizely);

Preview Mode Sends Real Data

When using Optimizely's Preview Mode to QA experiments, the track_layer_decision callback fires normally. Test decisions are sent to Mixpanel and appear in your production project's event stream and reports. Use Mixpanel's Live View to identify and filter out test sessions, or configure a separate Mixpanel project for QA.

Super Properties Persist Across Sessions

mixpanel.register() stores super properties in the browser's localStorage or cookie, which means experiment properties persist across sessions by default. If a visitor re-visits after an experiment has ended, their Mixpanel events will still carry the old experiment super property until it is explicitly cleared.

When you conclude an experiment, call mixpanel.unregister() to remove the super property:

// Call this when an experiment ends to prevent stale super properties
mixpanel.unregister('[Optimizely] Your Experiment Name');

If you want experiment context to apply only within the current session (not persist across visits), use mixpanel.register_once() instead of mixpanel.register() in the callback, or implement session-based logic using sessionStorage.

Masked Descriptive Names

If your Optimizely project has Mask descriptive names enabled (Settings > Privacy), the getDecisionObject() API returns hashed values instead of human-readable experiment and variation names. In this case, the callback falls back to numeric IDs, and super properties appear as [Optimizely] 12345678901234 = 98765432109876 in Mixpanel reports.

If you see 16-digit numeric IDs in your Mixpanel data, check the Mask descriptive names setting in your Optimizely project — this is the most common cause.

Data Volume Considerations

The "Experiment Viewed" event fires once per visitor per experiment per page load. On high-traffic sites running multiple concurrent experiments, this can generate substantial event volume in Mixpanel. If event volume is a concern:

  • Use only mixpanel.register() and mixpanel.people.set() and omit the mixpanel.track() call.

  • Filter experiments in the callback by adding a condition that checks campaignId against a list of experiments you want to track.

16-Digit IDs in Reports

If super property names show 16-digit numeric values (e.g., 1234567890123456) instead of readable experiment names, this indicates the Mask descriptive names feature is enabled in your Optimizely project settings, not a bug in the integration. Enabling this feature is irreversible per project — confirm with your Optimizely account team before enabling it.

Troubleshooting

Super Properties Not Appearing in Mixpanel

If "Experiment Viewed" events appear in Mixpanel but the super property is not attached to subsequent events:

  • Timing of register call: mixpanel.register() only affects events tracked after the call. Events tracked before the decision callback fires (e.g., a "Page Viewed" event fired on page load before the Optimizely decision) will not carry the experiment super property.

  • Check localStorage: Mixpanel stores super properties in localStorage under the key __mp_<token>. Use the browser console to inspect JSON.parse(localStorage.getItem('__mp_YOUR_TOKEN_HERE')) and verify the experiment property is present.

  • Mixpanel initialization timing: If mixpanel.init() is called after the decision callback fires, the register() call may not persist correctly.

Events Not Appearing in Mixpanel

If no experiment events appear in Mixpanel at all:

  • Mixpanel not initialized: Verify window.mixpanel exists in the console at page load. If it's undefined, the Mixpanel SDK is not loaded at the time the callback fires.

  • Integration not enabled: Confirm the integration is toggled on in Settings > Integrations and enabled for the specific experiment in Manage Campaign > Integrations.

  • Visitor not bucketed: The callback only fires when Optimizely makes an active bucketing decision. If the visitor does not meet the experiment's audience conditions, no decision fires.

  • Ad blockers: Privacy extensions commonly block requests to api.mixpanel.com. This causes silent data loss. Check the network tab and filter for api.mixpanel.com to verify requests are completing successfully.

Data Discrepancies Between Platforms

Differences between Optimizely visitor counts and Mixpanel event counts are expected and normal:

  • Counting unit: Optimizely counts unique visitors (cookie-based), while Mixpanel counts distinct IDs (device ID or user ID after identification). Identity resolution differences cause count divergence.

  • Ad blockers: Ad blockers may selectively block Mixpanel or Optimizely requests, skewing counts in either direction.

  • SPA navigation: In single-page applications, Optimizely may fire multiple decisions on client-side navigation while Mixpanel may track page views differently. Ensure your SPA event tracking model aligns.

  • Session attribution: Mixpanel and Optimizely attribute sessions and visitors using different identity models.

Expect 5–15% discrepancy between platforms. Investigate further if differences exceed 20%.

Cohort Sync Not Updating in ODP

If synced Mixpanel cohorts are not reflecting updated membership in Optimizely:

  • Sync schedule: Confirm whether the Mixpanel webhook export is configured as a one-time sync or a recurring schedule. One-time syncs do not update automatically.

  • Webhook delivery failures: Check the Mixpanel export logs for failed delivery attempts to the ODP webhook endpoint.

  • Cohort recalculation: Mixpanel cohorts recalculate on a delay. Very recently added users may not appear in the next sync window.

  • ODP attribute name: Verify the mixpanel_ prefixed attribute name in ODP matches what you are referencing in your Optimizely audience conditions.

Optimizely tips, straight to your inbox

Practical guides and patterns for experimentation practitioners. No spam, unsubscribe anytime.