# Mixpanel

The Mixpanel integration allows you to automatically send Superwall subscription and payment events to your Mixpanel project.

In the **Analytics** section within **Integrations**, you can connect your Mixpanel account to Superwall:

<img src="__img0" />

This integration provides two-way data flow:

1. **Event Tracking**: Sends detailed subscription lifecycle events to Mixpanel.
2. **User Profile Updates**: Updates user profiles with revenue data and transaction history.

Required Fields [#required-fields]

Fill out the following fields and **click** the **Enable Mixpanel** button at the bottom right to save your changes:

<img src="__img1" />

* **Region:** Data residency region for your Mixpanel project.
* **Project Token:** Your Mixpanel project token (Mixpanel → Settings → Project Settings → Project Token).
* **Total Spend Property:** The name of the user property to track cumulative spend.
* **Sales Reporting:** Whether to report Proceeds after store taxes & fees or Revenue. Choose between **Proceeds** (after store taxes & fees) or **Revenue**.

Features [#features]

* **Automatic Event Mapping**: Converts Superwall events to Mixpanel-friendly event names
* **Revenue Tracking**: Tracks both price (gross) and proceeds (net after fees)
* **User Profile Enrichment**: Maintains cumulative spend and transaction history
* **Multi-Region Support**: Works with US, EU, and IN data residency regions
* **Sandbox Isolation**: Separate tracking for production and sandbox events
* **Refund Handling**: Automatically adjusts revenue metrics for refunds

Configuration [#configuration]

Required Settings [#required-settings]

| Field                  | Description                             | Example                     |
| ---------------------- | --------------------------------------- | --------------------------- |
| `integration_id`       | Must be set to `"mixpanel"`             | `"mixpanel"`                |
| `region`               | Data residency region                   | `"US"`, `"EU"`, or `"IN"`   |
| `project_token`        | Your Mixpanel project token             | `"abc123def456..."`         |
| `total_spend_property` | User property name for cumulative spend | `"lifetime_revenue"`        |
| `sales_reporting`      | Which value to report                   | `"Revenue"` or `"Proceeds"` |

Optional Settings [#optional-settings]

| Field                   | Description                                    | Example       |
| ----------------------- | ---------------------------------------------- | ------------- |
| `sandbox_project_token` | Token for sandbox events (leave blank to skip) | `"xyz789..."` |

Example Configuration [#example-configuration]

```json
{
  "integration_id": "mixpanel",
  "region": "US",
  "project_token": "your_production_token_here",
  "sandbox_project_token": "your_sandbox_token_here",
  "total_spend_property": "lifetime_revenue",
  "sales_reporting": "Proceeds"
}
```

Event Mapping [#event-mapping]

Superwall events are transformed into standardized Mixpanel events with the `sw_` prefix:

Trial Events [#trial-events]

| Superwall Event                          | Mixpanel Event         | Description             |
| ---------------------------------------- | ---------------------- | ----------------------- |
| `initial_purchase` + `periodType: TRIAL` | `sw_trial_start`       | Trial period begins     |
| `cancellation` + `periodType: TRIAL`     | `sw_trial_cancelled`   | Trial cancelled         |
| `uncancellation` + `periodType: TRIAL`   | `sw_trial_uncancelled` | Trial reactivated       |
| `expiration` + `periodType: TRIAL`       | `sw_trial_expired`     | Trial ended             |
| `renewal` + `isTrialConversion: true`    | `sw_trial_converted`   | Trial converted to paid |

Intro Offer Events [#intro-offer-events]

| Superwall Event                          | Mixpanel Event               | Description                |
| ---------------------------------------- | ---------------------------- | -------------------------- |
| `initial_purchase` + `periodType: INTRO` | `sw_intro_offer_start`       | Intro offer begins         |
| `cancellation` + `periodType: INTRO`     | `sw_intro_offer_cancelled`   | Intro offer cancelled      |
| `uncancellation` + `periodType: INTRO`   | `sw_intro_offer_uncancelled` | Intro offer reactivated    |
| `expiration` + `periodType: INTRO`       | `sw_intro_offer_expired`     | Intro offer ended          |
| `renewal` + `periodType: INTRO`          | `sw_intro_offer_converted`   | Intro converted to regular |

Subscription Events [#subscription-events]

| Superwall Event                           | Mixpanel Event                | Description              |
| ----------------------------------------- | ----------------------------- | ------------------------ |
| `initial_purchase` + `periodType: NORMAL` | `sw_subscription_start`       | Subscription begins      |
| `renewal` + `periodType: NORMAL`          | `sw_renewal`                  | Subscription renewed     |
| `cancellation` + `periodType: NORMAL`     | `sw_subscription_cancelled`   | Subscription cancelled   |
| `uncancellation` + `periodType: NORMAL`   | `sw_subscription_uncancelled` | Subscription reactivated |
| `expiration` + `periodType: NORMAL`       | `sw_subscription_expired`     | Subscription ended       |
| `subscription_paused`                     | `sw_subscription_paused`      | Subscription paused      |
| `billing_issue`                           | `sw_billing_issue`            | Payment failed           |

Other Events [#other-events]

| Superwall Event            | Mixpanel Event             | Description       |
| -------------------------- | -------------------------- | ----------------- |
| `product_change`           | `sw_product_change`        | Plan changed      |
| `non_renewing_purchase`    | `sw_non_renewing_purchase` | One-time purchase |
| Any event with `price < 0` | `sw_refund`                | Refund processed  |

Event Properties [#event-properties]

Every Mixpanel event includes all fields from the Superwall webhook data object as properties:

Core Properties [#core-properties]

* `distinct_id`: User identifier (uses `originalAppUserId` or falls back to `originalTransactionId`)
* `time`: Unix timestamp in seconds
* `$insert_id`: Unique event ID (prevents duplicates)
* `token`: Your Mixpanel project token

Webhook Data Properties [#webhook-data-properties]

All fields from the webhook are included:

* `id`, `name`, `cancelReason`, `exchangeRate`
* `isSmallBusiness`, `periodType`, `countryCode`
* `price`, `proceeds`, `priceInPurchasedCurrency`
* `taxPercentage`, `commissionPercentage`, `takehomePercentage`
* `offerCode`, `isFamilyShare`, `expirationAt`
* `transactionId`, `originalTransactionId`, `originalAppUserId`
* `store`, `purchasedAt`, `currencyCode`, `productId`
* `environment`, `isTrialConversion`, `newProductId`
* `bundleId`, `ts`

User Profile Updates [#user-profile-updates]

The integration performs two profile updates for revenue events:

1\. Transaction History [#1-transaction-history]

Appends transaction details to the `$transactions` array:

```json
{
  "$transactions": {
    "$amount": 9.99,
    "$time": "2025-01-01T12:00:00.000Z",
    // Plus all webhook data fields
  }
}
```

2\. Cumulative Spend [#2-cumulative-spend]

Updates the total spend property (configurable):

```json
{
  "lifetime_revenue": 129.99  // Incremented by transaction amount
}
```

Revenue Reporting Options [#revenue-reporting-options]

Price vs Proceeds [#price-vs-proceeds]

The `sales_reporting` setting determines which value is used for revenue:

| Setting      | Value Used | Description                               |
| ------------ | ---------- | ----------------------------------------- |
| `"Revenue"`  | `price`    | Gross revenue before store fees and taxes |
| `"Proceeds"` | `proceeds` | Net revenue after store fees and taxes    |

Examples [#examples]

**Gross Revenue (Price):**

* Transaction price: $9.99
* Store commission (30%): $3.00
* Your proceeds: $6.99
* Reported to Mixpanel: **$9.99**

**Net Revenue (Proceeds):**

* Transaction price: $9.99
* Store commission (30%): $3.00
* Your proceeds: $6.99
* Reported to Mixpanel: **$6.99**

Sandbox Handling [#sandbox-handling]

With Sandbox Token [#with-sandbox-token]

If `sandbox_project_token` is configured:

* Production events → Production project
* Sandbox events → Sandbox project

Without Sandbox Token [#without-sandbox-token]

If `sandbox_project_token` is empty:

* Production events → Production project
* Sandbox events → **Skipped** (not sent to Mixpanel)

Refund Handling [#refund-handling]

Refunds are automatically detected when `price < 0`:

* Event type: `sw_refund`
* Transaction amount: Negative value
* Cumulative spend: Decremented by refund amount

Example:

* Original purchase: +$9.99
* Refund event: -$9.99
* Net effect on lifetime revenue: $0.00

Data Residency [#data-residency]

Mixpanel supports three data residency regions:

| Region | API Endpoint        | Use Case             |
| ------ | ------------------- | -------------------- |
| `US`   | api.mixpanel.com    | Default, global      |
| `EU`   | api-eu.mixpanel.com | GDPR compliance      |
| `IN`   | api-in.mixpanel.com | India data residency |

User Identification [#user-identification]

The integration uses the following hierarchy for user identification:

1. **Primary**: `originalAppUserId` (if available)
2. **Fallback**: `originalTransactionId` (always present)

This ensures consistent user tracking even for:

* Legacy users without app user IDs
* Family sharing scenarios
* Cross-platform subscriptions

Testing the Integration [#testing-the-integration]

1\. Trigger Sandbox Events [#1-trigger-sandbox-events]

* iOS: Use TestFlight with a sandbox Apple ID. StoreKit Configuration files do not generate App Store Server Notifications, so webhooks and downstream integrations won't fire.
* Google Play: Use license test accounts to perform sandbox purchases.
* Stripe: Use Stripe Test Mode to create sandbox transactions.

2\. Verify in Mixpanel [#2-verify-in-mixpanel]

Check your Mixpanel project:

1. Live View → Verify events arriving
2. Users → Check profile updates
3. Reports → Confirm revenue tracking

Troubleshooting [#troubleshooting]

Events Not Appearing [#events-not-appearing]

1. **Check Token**: Verify project token is correct
2. **Check Region**: Ensure region matches your Mixpanel project
3. **Check Environment**: Sandbox events need sandbox token
4. **Check Distinct ID**: User must have valid identifier

Revenue Not Tracking [#revenue-not-tracking]

1. **Check Sales Reporting**: Verify Price vs Proceeds setting
2. **Check Property Name**: Confirm `total_spend_property` exists
3. **Check Event Type**: Only revenue events update spend
4. **Check Refunds**: Negative amounts decrease total

Duplicate Events [#duplicate-events]

The integration uses `$insert_id` to prevent duplicates:

* Format: `eventId-eventName`
* Example: `abc123-renewal`

Mixpanel automatically deduplicates events with the same `$insert_id`.

Best Practices [#best-practices]

1. **Use Consistent User IDs**: Send user IDs to app stores for better tracking
2. **Set Up Both Tokens**: Configure sandbox token for complete testing
3. **Choose Revenue Model**: Decide between gross (Price) vs net (Proceeds)
4. **Monitor Both Projects**: Check production and sandbox regularly
5. **Handle Refunds**: Ensure your analytics account for negative revenue

Rate Limits [#rate-limits]

Mixpanel has the following limits:

* **Events**: 2,000 requests/second
* **Profile Updates**: 2,000 requests/second
* **Batch Size**: 2MB per request

The integration sends events individually, well within these limits.

Data Privacy [#data-privacy]

* **PII Handling**: User IDs are pseudonymous by default
* **GDPR Compliance**: Use EU region for European users
* **Data Retention**: Follows your Mixpanel project settings
* **Deletion Requests**: Handle via Mixpanel's privacy tools