# Mixpanel

- Tool: Bubble
- Difficulty: Intermediate
- Time required: 2–3 hours
- Last updated: July 2026

## TL;DR

Connect Bubble to Mixpanel in two directions using the API Connector: send product analytics events from your Bubble app to Mixpanel via the HTTP ingestion API (project token marked Private in a POST body), and read analytics data back into Bubble using the Query API with Mixpanel service account Basic Auth credentials kept Private. Unlike FlutterFlow or Lovable, Bubble's API Connector handles both directions server-side with zero extra infrastructure.

## Two Directions: Sending Events to Mixpanel and Reading Analytics Back into Bubble

Mixpanel's Bubble integration serves two different teams with two different needs.

Product teams need to send events from their Bubble app to Mixpanel so they can analyze user behavior in Mixpanel's funnel and retention reports. This direction uses the Mixpanel HTTP ingestion API — a simple POST with your project token and an events array. Bubble's API Connector handles this entirely server-side with a Private project token, firing the call from any Bubble Workflow action.

Analytics teams need to pull aggregated Mixpanel data into a Bubble dashboard — showing conversion rates, top events, or user counts inside an internal Bubble tool without giving everyone access to the Mixpanel UI. This direction uses the Mixpanel Query API with service account credentials, also handled natively by Bubble's API Connector.

The critical distinction to keep in your notes:
- Project token: a semi-public key used only for event ingestion. Found in Mixpanel Project Settings → Project Details → Project Token.
- Service account credentials: a username + secret key pair used only for the Query API. Created in Mixpanel Project Settings → Service Accounts.

Using the project token in a Query API Basic Auth header returns 401. Using service account credentials in an ingestion POST returns errors too. They are different keys for different APIs — and this confusion is the most common Mixpanel setup mistake.

Bubble's advantage here versus FlutterFlow (which requires a Cloud Function for service account queries) or Lovable (Edge Function): both directions work natively through the API Connector. No external infrastructure needed.

## Before you start

- A Mixpanel account with an active project — free plan works for event sending; reading Query API data may require a paid Mixpanel plan (check your plan's API access)
- Your Mixpanel Project Token (from Mixpanel → Project Settings → Project Details → Project Token) for event ingestion
- A Mixpanel Service Account (from Mixpanel → Project Settings → Service Accounts → Add Service Account) for Query API access
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' by Bubble → Install)
- Your Mixpanel project's data residency region confirmed (US or EU) — check Mixpanel Project Settings → Data Residency to know which base URL to use

## Step-by-step guide

### 1. Locate Your Project Token and Create a Service Account in Mixpanel

Before configuring anything in Bubble, gather the two sets of Mixpanel credentials you will need.

Project Token (for event ingestion):
Log into Mixpanel → click your project name at the top → Project Settings → Project Details tab. Your Project Token is shown here — it is a 32-character string that looks like 'abc123def456...'. Copy it. This token identifies which Mixpanel project receives events but is NOT a secret key in the traditional sense — it does not grant access to read data. However, you should still mark it Private in the Bubble API Connector to prevent it from being logged.

Service Account (for Query API):
Go to Project Settings → Service Accounts tab → Add Service Account. Give it a name like 'Bubble Integration'. Mixpanel generates a username (format: service-account-name.12345.mp-service-account) and a secret key. Copy both immediately — the secret key is shown only once. These two values form the Basic Auth credentials for the Query API.

Also confirm your project's data residency at this point. Go to Project Settings → Data Residency. This tells you whether your Mixpanel project stores data in the US (use api.mixpanel.com and mixpanel.com/api) or in the EU (use api-eu.mixpanel.com and eu.mixpanel.com/api). Configuring the wrong region is the most common cause of silent failures — events appear to send successfully (HTTP 200) but never appear in Mixpanel.

Note the project_id (a numeric ID shown in Project Settings) — you will need it as a query parameter for the Query API.

```
{
  "credentials_to_gather": {
    "project_token": "Project Settings → Project Details → Project Token (32-char string)",
    "service_account_username": "Project Settings → Service Accounts → create → copy username",
    "service_account_secret": "from the same dialog — shown only once",
    "project_id": "Project Settings → Project Details → Project ID (numeric)",
    "data_residency": "Project Settings → Data Residency → US or EU"
  },
  "base_urls_by_region": {
    "us_ingestion": "https://api.mixpanel.com",
    "eu_ingestion": "https://api-eu.mixpanel.com",
    "us_query_api": "https://mixpanel.com/api/query",
    "eu_query_api": "https://eu.mixpanel.com/api/query"
  }
}
```

**Expected result:** A project_token, service_account_username, service_account_secret, project_id, and confirmed data residency region — all ready to configure in the Bubble API Connector.

### 2. Configure API Connector Group 1: Send Events to Mixpanel

In your Bubble app, click the Plugins tab → API Connector → 'Add another API'. Name this group 'Mixpanel Events'. Set the base URL to the correct ingestion URL for your data residency region:
- US: https://api.mixpanel.com
- EU: https://api-eu.mixpanel.com

Click 'Add another call'. Name it 'Send Event'. Method: POST. Endpoint path: /import.

Add one query parameter: token = your project_token. Check the Private checkbox on this parameter — this prevents the token from being logged in Bubble's API Connector response panel.

Add two shared headers:
- Content-Type: application/json
- Accept: application/json

For authentication, select 'None' in the API Connector authentication dropdown (the project token in the query parameter handles authentication for this endpoint).

Set the body type to JSON. The /import endpoint accepts an array of event objects. A minimal valid event:
[
  {
    "event": "{{event_name}}",
    "properties": {
      "distinct_id": "{{distinct_id}}",
      "time": {{unix_timestamp}},
      "$insert_id": "{{insert_id}}",
      "{{property_key}}": "{{property_value}}"
    }
  }
]

Key required fields:
- event: the event name string (e.g., 'sign_up', 'purchase_completed')
- properties.distinct_id: the user identifier — use the Current User's unique ID in Bubble for logged-in users. This is how Mixpanel groups events by user — missing or wrong distinct_id makes user-level analysis impossible.
- properties.time: Unix timestamp in seconds (Bubble's Current date/time formatted as Unix timestamp)
- properties.$insert_id: a unique ID per event to prevent duplicate ingestion — use Bubble's 'Generate random string' or the unique ID of a relevant database record

Set 'Use as' to 'Action'.

Click 'Initialize call' with test values. Mixpanel's /import endpoint always returns HTTP 200 with body {"code": 200, "num_records_imported": 1, "status": "OK"} for valid events — but also returns HTTP 200 for many invalid events. After initialization succeeds, verify event delivery in Mixpanel → Live View within a few seconds.

```
{
  "method": "POST",
  "url": "https://api.mixpanel.com/import",
  "query_params": [
    { "key": "token", "value": "<your_project_token>", "private": true }
  ],
  "headers": [
    { "key": "Content-Type", "value": "application/json" },
    { "key": "Accept", "value": "application/json" }
  ],
  "body": [
    {
      "event": "{{event_name}}",
      "properties": {
        "distinct_id": "{{distinct_id}}",
        "time": "{{unix_timestamp}}",
        "$insert_id": "{{insert_id}}",
        "plan": "{{user_plan}}",
        "source": "bubble_app"
      }
    }
  ],
  "use_as": "Action"
}
```

**Expected result:** A 'Send Event' action available in Bubble Workflows, with a successful Initialize response showing {"code": 200, "num_records_imported": 1}. Confirmed by seeing the test event in Mixpanel → Live View.

### 3. Trigger Mixpanel Events from Bubble Workflows

With the Send Event call configured, attach it to meaningful user actions throughout your Bubble app. The most impactful events to track from the beginning:

1. Sign-up / Account creation: Workflow event 'When user signs up' → 'Plugins → Mixpanel Events - Send Event' with event_name = 'sign_up' and distinct_id = Current User's unique ID.

2. Key feature activation: Workflow event 'When Button [Start Project] is clicked' → Send Event with event_name = 'project_started' and relevant properties (project type, user plan).

3. Conversion / Purchase: Workflow event on checkout completion → Send Event with event_name = 'subscription_started' and properties (plan name, price, source).

4. Feature usage: Workflow event 'When repeating group cell button is clicked' → Send Event tracking which items users interact with.

For distinct_id in Bubble:
- Logged-in users: use 'Current User's unique ID' (a UUID Bubble assigns to every user)
- Anonymous users (not logged in): Mixpanel requires some distinct_id even for anonymous events. Use a Bubble custom state to store a generated UUID on first page load, and use that value as distinct_id. When the user later logs in, call Mixpanel's $identify event to merge the anonymous profile with the authenticated user's profile.

For the time property: Bubble does not natively output Unix timestamps in seconds, but you can calculate it: 'Current date/time formatted as Unix' — Bubble's 'formatted as' date option includes 'Unix time in milliseconds'. Divide by 1000 in Bubble's math operator to get seconds, or use the optional 'time_offset' approach.

Property best practices: use snake_case property names (e.g., 'plan_type' not 'Plan Type'), use consistent property values (always 'pro' not sometimes 'Pro' and sometimes 'PRO'), and include properties that will be useful for segmentation later rather than trying to add them retroactively.

```
// Workflow examples:
//
// Event: User signs up
// Action: Plugins → Mixpanel Events - Send Event
//   event_name = sign_up
//   distinct_id = Current User's unique ID
//   time = [Current date/time as Unix ms / 1000]
//   insert_id = Current User's unique ID + "_signup"
//   plan = Current User's plan (text)
//
// Event: Button 'Start Project' clicked
// Action: Plugins → Mixpanel Events - Send Event
//   event_name = project_started
//   distinct_id = Current User's unique ID
//   time = [Current date/time as Unix ms / 1000]
//   insert_id = New Project's unique ID + "_started"
//   project_type = Dropdown 'Project Type's value
//
// Event: Subscription checkout completed
// Action: Plugins → Mixpanel Events - Send Event
//   event_name = subscription_started
//   distinct_id = Current User's unique ID
//   time = [Current date/time as Unix ms / 1000]
//   insert_id = Stripe Charge ID
//   plan = Selected plan name
//   amount = Order total
```

**Expected result:** Custom product events firing from Bubble Workflows and appearing in Mixpanel Live View within seconds — confirming the server-side event ingestion is working correctly before building the read direction.

### 4. Configure API Connector Group 2: Mixpanel Query API with Service Account Auth

Create a second API group in the API Connector for reading analytics data from Mixpanel. Click 'Add another API'. Name it 'Mixpanel Query API'. Set the base URL to the correct Query API URL for your data residency:
- US: https://mixpanel.com/api/query
- EU: https://eu.mixpanel.com/api/query

In the API Connector authentication settings, select 'Basic Auth'. Enter:
- Username: your Mixpanel service account username (format: name.12345.mp-service-account)
- Password: your Mixpanel service account secret key

Both will be automatically base64-encoded by Bubble's API Connector Basic Auth system and sent as an Authorization: Basic [base64] header. Mark both as Private.

Add a shared query parameter: project_id = your numeric Mixpanel project ID. This is required on all Query API calls.

Add your first Query API call. Name it 'Get Event Segmentation'. Method: GET. Endpoint path: /segmentation. Parameters:
- event: the event name to query (e.g., 'sign_up') — dynamic
- from_date: start date in YYYY-MM-DD format — dynamic
- to_date: end date in YYYY-MM-DD format — dynamic
- unit: 'day' (or 'week', 'month')
- type: 'general'

The response format:
{"data": {"series": [[count_day1, count_day2, ...]], "xValues": ["YYYY-MM-DD", ...]}, "legend_size": 1}

Set 'Use as' to 'Action' (or Data if you want to bind directly to a Repeating Group). Click 'Initialize call' with real values. If you see 401 Unauthorized, confirm you are using service account credentials (NOT the project token) in the Basic Auth fields — these are completely separate from the ingestion project token.

```
{
  "api_group": "Mixpanel Query API",
  "base_url_us": "https://mixpanel.com/api/query",
  "base_url_eu": "https://eu.mixpanel.com/api/query",
  "authentication": {
    "type": "Basic Auth",
    "username": "<service_account_username> [Private]",
    "password": "<service_account_secret> [Private]"
  },
  "shared_params": [
    { "key": "project_id", "value": "{{your_numeric_project_id}}" }
  ],
  "segmentation_call": {
    "method": "GET",
    "path": "/segmentation",
    "params": {
      "event": "{{event_name}}",
      "from_date": "{{from_date}}",
      "to_date": "{{to_date}}",
      "unit": "day",
      "type": "general"
    },
    "use_as": "Action"
  }
}
```

**Expected result:** A 'Get Event Segmentation' call that initializes successfully with real data, returning an object with 'series' (array of count arrays) and 'xValues' (date strings). The call is available as an Action in Bubble Workflows.

### 5. Build a Workflow to Display Mixpanel Analytics Data in Bubble

With both API Connector groups configured, you can now build a Bubble dashboard page that displays Mixpanel analytics data.

Create a data type 'Mixpanel Metric' with fields: event_name (text), date (date), count (number). This will store the query results.

Add a Repeating Group to your page. Type of content: Mixpanel Metric. Add text elements for date and count in the cell. Optionally add a chart element using Bubble's built-in chart, with the data source set to 'Search for Mixpanel Metrics' sorted by date ascending.

Create a Workflow event: 'When page is loaded'.

Action 1: 'Plugins → Mixpanel Query API - Get Event Segmentation'. Set event to 'sign_up' (or your chosen event), from_date to 30 days ago (formatted as YYYY-MM-DD), to_date to yesterday.

Action 2: 'Delete a list of things' — clear old Mixpanel Metric records to prevent stale data.

Action 3: For each day's data, create a new Mixpanel Metric record. The response structure has 'xValues' as a list of date strings and 'data.series[0]' as the matching count array — in Bubble, reference them by index position.

Bind the Repeating Group to 'Search for Mixpanel Metrics, sorted by date ascending'.

For more complex Query API responses (funnels, retention): Mixpanel's Query API returns different response shapes per endpoint. Test each endpoint's response shape carefully in the Initialize call before building Workflows around it.

Caching: Mixpanel query data changes slowly (typically updated every few minutes but you only need hourly or daily granularity for a dashboard). Cache results with a fetched_at timestamp and only re-query Mixpanel when the cache is older than 1 hour. This significantly reduces WU consumption for dashboard pages loaded frequently by multiple team members.

Add privacy rules to the Mixpanel Metric data type in Data tab → Privacy to restrict access to authorized users.

```
// Workflow: When page is loaded
//
// Action 1: Plugins → Mixpanel Query API - Get Event Segmentation
//   event = sign_up
//   from_date = [Current date/time - 30 days formatted YYYY-MM-DD]
//   to_date = [Current date/time - 1 day formatted YYYY-MM-DD]
//   unit = day
//   type = general
//
// Action 2: Delete all Mixpanel Metric records
//
// Action 3: Create Mixpanel Metric for each date in Action 1's xValues list
//   date = [current index's xValues item, parsed as date]
//   count = [current index's data.series[0] item]
//   event_name = sign_up
//
// Repeating Group data source:
//   Search for Mixpanel Metrics
//   Sorted by date ascending
//
// Chart data: same search, x-axis = date, y-axis = count
```

**Expected result:** A Bubble dashboard page displaying Mixpanel event count data over time, queried from the Mixpanel Query API and cached in the Bubble database — refreshed on page load with results visible in a Repeating Group and/or chart.

## Best practices

- Always mark your project_token and service account credentials as Private in the API Connector — while the project token is semi-public, logging it exposes which Mixpanel project your app uses and makes credential rotation harder.
- Confirm your Mixpanel project's data residency (US vs EU) before writing a single API Connector call. The wrong region causes silent failures — Mixpanel returns HTTP 200 for ingestion calls to the wrong endpoint but the events are discarded.
- Always include distinct_id on every event — it is how Mixpanel associates events with users. For logged-in Bubble users, use Current User's unique ID. Without distinct_id, events are attributed to rotating anonymous profiles and user-level analysis is impossible.
- Include $insert_id on every ingestion event to prevent duplicate counting — Bubble Workflow retries or network issues can cause the same event to be sent twice, and Mixpanel uses $insert_id to deduplicate within a 24-hour deduplication window.
- Cache Query API results in the Bubble database with a fetched_at timestamp. Mixpanel dashboard data changes slowly — re-querying on every page load wastes WUs and hits API rate limits unnecessarily on busy Bubble apps.
- Add Bubble privacy rules to any data type storing Mixpanel query results. Without privacy rules, Bubble's public Data API exposes analytics data to anyone who knows the data type name.
- Use consistent, lowercase snake_case naming for Mixpanel event names and property keys (e.g., 'project_created' not 'Project Created'). Inconsistent naming creates duplicate events in Mixpanel's event dictionary and makes queries harder.
- Send events in batches where possible — the Mixpanel /import endpoint accepts arrays of up to 2,000 events per call. If you are sending multiple events from a single Bubble Workflow (e.g., importing historical data), batch them into one API call rather than making individual calls per event to reduce WU consumption.

## Use cases

### Product Event Tracking in a Bubble SaaS App

Fire Mixpanel events whenever users complete key actions in your Bubble app — account creation, feature activation, plan upgrade, invitation sent — so your product team can analyze which features drive retention and which flows have the highest drop-off.

Prompt example:

```
When a Bubble user completes their first project in the app, send a 'first_project_completed' event to Mixpanel with the project type and the user's plan as event properties.
```

### Funnel Conversion Rate Dashboard Inside Bubble

Pull Mixpanel funnel conversion data into a Bubble page to display the signup-to-paid conversion rate and step-by-step funnel drop-off percentages — visible to the whole team inside the Bubble internal tool without requiring everyone to log into Mixpanel.

Prompt example:

```
Query the Mixpanel funnel for the signup-to-subscription flow for the last 30 days and display conversion rates per step in a Bubble Repeating Group.
```

### Feature Usage Monitoring in a Bubble Admin Panel

Display a live table of top events by count for the last 7 days inside a Bubble admin panel — giving the team visibility into which features are actively used and which have engagement drops without opening Mixpanel separately.

Prompt example:

```
Show the top 10 most frequent Mixpanel events from the last 7 days sorted by count in a Bubble admin dashboard, refreshed on page load.
```

## Troubleshooting

### Mixpanel events do not appear in Live View or reports after sending from Bubble — but the API call returns HTTP 200

Cause: Either the project token is wrong (pointing to a different Mixpanel project), the data residency region is wrong (sending to US endpoint while the project is EU-region), or the events have a malformed distinct_id or event name that causes Mixpanel to silently discard them.

Solution: First, confirm data residency: go to Mixpanel Project Settings → Data Residency. If the project is EU-region, change the API Connector base URL from api.mixpanel.com to api-eu.mixpanel.com. Second, verify the project_token matches the project you are checking in Live View. Third, switch to Mixpanel's debug endpoint (https://api.mixpanel.com/import?verbose=1) temporarily — this returns more detailed error information in the response body. Check that distinct_id is a non-empty string and that the event name contains only allowed characters.

```
{
  "debug_url": "https://api.mixpanel.com/import?token=YOUR_TOKEN&verbose=1",
  "note": "verbose=1 makes the API return detailed error information instead of a generic {code: 200}"
}
```

### Query API call returns 401 Unauthorized

Cause: You are using the project token (from Project Settings → Project Token) in the Basic Auth credentials instead of the service account username and secret. These are completely different credential sets for different APIs.

Solution: Go to Mixpanel Project Settings → Service Accounts tab. Create a new service account if you have not done so. Copy the username (format: name.12345.mp-service-account) and the secret key (visible only on creation). In the API Connector → Mixpanel Query API → Authentication, replace the credentials with these service account values. The project token belongs ONLY in the ingestion call's query parameter, never in Basic Auth for the Query API.

### distinct_id is wrong for some users and their events are attributed to anonymous or incorrect profiles in Mixpanel

Cause: In Bubble, 'Current User's unique ID' is the correct value for logged-in users. If the event fires before user login (or for anonymous users), the distinct_id defaults to an empty string or placeholder, causing events to be attributed to an anonymous profile that cannot be merged later.

Solution: For logged-in users: always use 'Current User's unique ID' as distinct_id. For anonymous users: on first page load, generate a UUID (using Bubble's 'Generate random string' action with format 'Random characters', length 36) and store it in a Custom State. Use this UUID as distinct_id for all pre-login events. After login, send a Mixpanel '$identify' event (POST to /import with event name '$identify' and properties including 'distinct_id' as the logged-in user ID and '$anon_distinct_id' as the anonymous UUID) to merge the pre-login and post-login event streams.

### Mixpanel Query API segmentation call times out in Bubble

Cause: Complex Mixpanel queries (large date ranges, JQL queries, or high-cardinality segmentation) can take more than 30 seconds — Bubble's Workflow timeout limit.

Solution: Reduce the date range in the query (e.g., 7 days instead of 90 days), use simpler segmentation (no property breakdowns if not needed), or use Mixpanel's 'limit' parameter where available. For large historical queries, consider pre-computing and caching the data with a Bubble Backend Workflow that runs on a schedule rather than on user-triggered page loads.

### 'There was an issue setting up your call' when initializing the Query API call in the API Connector

Cause: The Initialize call failed — likely because the project_id query parameter is missing or wrong, the from_date or to_date format is incorrect (must be YYYY-MM-DD), or the service account credentials are incorrect.

Solution: Verify the project_id is the numeric ID from Mixpanel Project Settings (not the token). Check date format strings are exactly YYYY-MM-DD with no time component. Confirm service account credentials are entered correctly in the Basic Auth fields — try logging into Mixpanel's API with the same credentials using a tool like Postman or curl to confirm they are valid before troubleshooting Bubble.

## Frequently asked questions

### What is the difference between the Mixpanel project token and a service account?

The project token is a semi-public identifier used only for event ingestion — it tells Mixpanel which project to store events in but does not grant access to read any data. The service account is a username + secret pair created in Project Settings → Service Accounts; it grants programmatic read access to Mixpanel's Query API for fetching analytics reports. Using the project token in Query API Basic Auth always returns 401, because they serve completely different authentication purposes.

### Mixpanel returns HTTP 200 but my events never appear. What is wrong?

The most common cause is a data residency mismatch — if your Mixpanel project is in the EU region but you are sending to api.mixpanel.com (the US endpoint), Mixpanel accepts the request and returns 200 but silently discards the events. Go to Mixpanel Project Settings → Data Residency to confirm your region, then update the API Connector base URL to api-eu.mixpanel.com for EU projects. Also verify the project_token matches the project you are checking in Live View.

### How do I track anonymous users in Bubble before they log in?

On first page load, use a Bubble Workflow to generate a random UUID (Bubble's 'Generate random string' action) and store it in a Custom State (not the database, as anonymous users don't have a record). Use this UUID as distinct_id for all pre-login Mixpanel events. When the user logs in, send a Mixpanel '$identify' event with distinct_id equal to the logged-in user's ID and a $anon_distinct_id property equal to the anonymous UUID — this merges the pre-login and post-login event histories in Mixpanel.

### Can I use the free Mixpanel plan with Bubble?

Yes, for event sending — the /import ingestion API is available on all Mixpanel plans including free. Query API access for reading data may be restricted on the free plan depending on Mixpanel's current plan terms (verify at mixpanel.com/pricing). The free plan allows up to 20 million events per month for ingestion, which is sufficient for most Bubble apps starting out.

### Can Bubble send events to Mixpanel on behalf of multiple users at once (bulk import)?

Yes. The Mixpanel /import endpoint accepts an array of up to 2,000 events per request. In Bubble, you can build a Workflow that iterates over a list of database records and sends them as a batched events array to Mixpanel in one API call. This is much more efficient for historical data imports than sending one event per API call.

### How do I verify that my Mixpanel events have the correct properties before going to production?

Use Mixpanel's Live View feature (Mixpanel → Live View in the left sidebar) which shows events in near real-time as they arrive. Click on a specific event in Live View to see all properties. You can also use the 'Events' tab in Mixpanel to inspect event schemas. Additionally, set a distinct_id for your own test account and use Mixpanel's User Lookup feature (Users tab → search for your distinct_id) to see all events attributed to you — this is the most reliable way to validate the full event tracking implementation before launch.

---

Source: https://www.rapidevelopers.com/bubble-integrations/mixpanel
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/mixpanel
