Mixpanel
Server-side event delivery to Mixpanel for product analytics, people profiles, and group analytics via the official mixpanel Node.js SDK.
Mixpanel is a server destination in the walkerOS flow:
Sends events server-side to Mixpanel via `/track` (real-time, up to 5 days old) or `/import` (historical, any age). Supports people profile operations, group analytics, and identity resolution.
Installation
npm install @walkeros/server-destination-mixpanel- Integrated
- Bundled
import { startFlow } from '@walkeros/collector';
import { destinationMixpanel } from '@walkeros/server-destination-mixpanel';
await startFlow({
destinations: {
mixpanel: {
code: destinationMixpanel,
config: {
settings: {
apiKey: 'YOUR_PROJECT_TOKEN',
},
},
},
},
});Add to your flow.json destinations:
"destinations": {
"mixpanel": {
"package": "@walkeros/server-destination-mixpanel",
"import": "destinationMixpanel",
"config": {
"settings": {
"apiKey": "YOUR_PROJECT_TOKEN"
}
}
}
}Configuration
This destination uses the standard destination config wrapper (consent, data, env, id, ...). For the shared fields see destination configuration. Package-specific fields live under config.settings and are listed below.
Settings
| Property | Type | Description | More |
|---|---|---|---|
apiKey | string | Your Mixpanel project token. Find it in Project Settings > Access Keys. Passed as the first argument to Mixpanel.init(). | |
secret | string | API secret for the /import endpoint (historical data). Required when useImport is true. | |
host | string | Mixpanel API host. Default: 'api.mixpanel.com' (US). Use 'api-eu.mixpanel.com' (EU) or 'api-in.mixpanel.com' (India). | |
protocol | string | Protocol for API requests. Default: 'https'. | |
keepAlive | boolean | Reuse HTTP connections. Default: true. | |
geolocate | boolean | Parse IP for geolocation. Default: false. Server IP caveat: all users map to server location unless $ip is overridden. | |
debug | boolean | Enable SDK debug logging. Default: false. | |
verbose | boolean | Enable verbose request logging. Default: false. | |
test | boolean | Enable dry-run mode. Default: false. | |
useImport | boolean | Use /import endpoint instead of /track. Accepts events of any age (no 5-day limit). Requires secret for authentication. | |
identify | any | walkerOS mapping value resolving to { distinctId, alias? }. distinctId is passed as distinct_id on every SDK call. | |
include | any | Event data sections to flatten into track() properties. Example: ['data', 'globals']. Sections are prefixed (data_, globals_, etc.). |
Mapping
Per-event rules under config.mapping. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.
| Property | Type | Description | More |
|---|---|---|---|
identify | any | Per-event identity mapping. Resolves to { distinctId, alias? }. distinctId is passed as distinct_id to all SDK calls. | |
people | any | Per-event people operations. Resolves to an object with any of: set, set_once, increment, append, union, remove, unset, delete_user. Each key fires a separate mp.people.* call with distinct_id as first arg. | |
group | any | Per-event group association. Resolves to { key, id }. The group key/id is added as a track() property. | |
groupProfile | any | Per-event group profile operations. Resolves to { key, id, set?, set_once?, union?, remove?, unset?, delete? }. Fires mp.groups.* calls. | |
useImport | any | Per-event import flag. When truthy, uses mp.import() instead of mp.track() for this rule. |
Examples
Alias before track
A user login merges a prior anonymous id into the new user id via mp.alias before sending the track event.
{
"name": "user login",
"data": {
"user_id": "new-user-456",
"anon_id": "anon-789"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "e73541cd9db18c94",
"trigger": "test",
"entity": "user",
"action": "login",
"timestamp": 1700000108,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"settings": {
"identify": {
"map": {
"distinctId": "data.user_id",
"alias": "data.anon_id"
}
}
}
}mp.alias("new-user-456", "anon-789");
mp.track("user login", {
"distinct_id": "new-user-456"
})Common people operations
A profile update exercises the common Mixpanel people vocabulary: set, set_once, increment, append, union, remove, and unset. delete_user is not covered by this example.
{
"name": "profile update",
"data": {
"name": "Jane Doe",
"email": "jane@acme.com",
"page": "/docs/getting-started",
"removed_tag": "trial",
"source": "referral"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "853d438fcd855d5c",
"trigger": "test",
"entity": "profile",
"action": "update",
"timestamp": 1700000105,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"silent": true,
"settings": {
"identify": {
"map": {
"distinctId": "user.id"
}
},
"people": {
"map": {
"set": {
"map": {
"name": "data.name",
"email": "data.email"
}
},
"set_once": {
"map": {
"signup_source": "data.source"
}
},
"increment": {
"map": {
"page_views": {
"value": 1
}
}
},
"append": {
"map": {
"visited_pages": "data.page"
}
},
"union": {
"map": {
"unique_tags": {
"value": [
"active"
]
}
}
},
"remove": {
"map": {
"tags": "data.removed_tag"
}
},
"unset": {
"value": [
"old_plan"
]
}
}
}
}
}mp.people.set("us3r", {
"name": "Jane Doe",
"email": "jane@acme.com"
});
mp.people.set_once("us3r", {
"signup_source": "referral"
});
mp.people.increment("us3r", {
"page_views": 1
});
mp.people.append("us3r", {
"visited_pages": "/docs/getting-started"
});
mp.people.union("us3r", {
"unique_tags": [
"active"
]
});
mp.people.remove("us3r", {
"tags": "trial"
});
mp.people.unset("us3r", [
"old_plan"
])Group profile
A company update sets Mixpanel group profile properties via groups.set and groups.set_once.
{
"name": "company update",
"data": {
"company_id": "acme-inc",
"company_name": "Acme, Inc.",
"plan": "enterprise",
"employee_count": 250,
"founded_year": 2010
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "ca94e9000f301bf0",
"trigger": "test",
"entity": "company",
"action": "update",
"timestamp": 1700000106,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"silent": true,
"settings": {
"groupProfile": {
"map": {
"key": {
"value": "company_id"
},
"id": "data.company_id",
"set": {
"map": {
"name": "data.company_name",
"plan": "data.plan",
"employee_count": "data.employee_count"
}
},
"set_once": {
"map": {
"founded": "data.founded_year"
}
}
}
}
}
}mp.groups.set("company_id", "acme-inc", {
"name": "Acme, Inc.",
"plan": "enterprise",
"employee_count": 250
});
mp.groups.set_once("company_id", "acme-inc", {
"founded": 2010
})Default track
A walkerOS event is forwarded to Mixpanel as a track call with the user id as the distinct_id.
{
"name": "product view",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"detail",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [],
"consent": {
"functional": true
},
"id": "a0fe0f4b43e3b9ff",
"trigger": "load",
"entity": "product",
"action": "view",
"timestamp": 1700000100,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}mp.track("product view", {
"distinct_id": "us3r"
})Historical import
Setting useImport routes the event through mp.import for backfilling historical Mixpanel data.
{
"name": "order complete",
"data": {
"total": 99.99
},
"context": {
"shopping": [
"complete",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "product",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "product",
"data": {
"id": "cc",
"name": "Cool Cap",
"size": "one size",
"price": 42
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "gift",
"data": {
"name": "Surprise"
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
}
],
"consent": {
"functional": true
},
"id": "ea6849ad310340be",
"trigger": "load",
"entity": "order",
"action": "complete",
"timestamp": 1700000107,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}mp.import("order complete", 1700000107, {
"distinct_id": "us3r"
})Per-event identify
A mapping-level identify overrides the destination default to resolve the distinct_id from event data.
{
"name": "user login",
"data": {
"user_id": "resolved-id",
"plan": "premium"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "3ad974850581afc3",
"trigger": "test",
"entity": "user",
"action": "login",
"timestamp": 1700000102,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"settings": {
"identify": {
"map": {
"distinctId": "data.user_id"
}
}
}
}mp.track("user login", {
"distinct_id": "resolved-id"
})Track with group
A group key and id are attached as a Mixpanel track property so the event is associated with a company or account.
{
"name": "page view",
"data": {
"company_id": "acme"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"pagegroup": "docs"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "6130db026df9b14c",
"trigger": "load",
"entity": "page",
"action": "view",
"timestamp": 1700000103,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"settings": {
"group": {
"map": {
"key": {
"value": "company_id"
},
"id": "data.company_id"
}
}
}
}mp.track("page view", {
"distinct_id": "us3r",
"company_id": "acme"
})Track with include
A destination-level include flattens the event data section into prefixed Mixpanel track properties.
{
"name": "product view",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"detail",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [],
"consent": {
"functional": true
},
"id": "b6b532f9fd316eb1",
"trigger": "load",
"entity": "product",
"action": "view",
"timestamp": 1700000101,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}mp.track("product view", {
"distinct_id": "us3r",
"data_id": "ers",
"data_name": "Everyday Ruck Snack",
"data_color": "black",
"data_size": "l",
"data_price": 420
})User login people
A user login fires Mixpanel people.set, set_once, and increment operations without sending a track event.
{
"name": "user login",
"data": {
"user_id": "new-user-123",
"plan": "premium",
"company": "Acme",
"email": "user@acme.com"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "235553b8d1c79669",
"trigger": "test",
"entity": "user",
"action": "login",
"timestamp": 1700000104,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"silent": true,
"settings": {
"identify": {
"map": {
"distinctId": "data.user_id"
}
},
"people": {
"map": {
"set": {
"map": {
"plan": "data.plan",
"company": "data.company",
"email": "data.email"
}
},
"set_once": {
"map": {
"first_login": "timestamp"
}
},
"increment": {
"map": {
"login_count": {
"value": 1
}
}
}
}
}
}
}mp.people.set("new-user-123", {
"plan": "premium",
"company": "Acme",
"email": "user@acme.com"
});
mp.people.set_once("new-user-123", {
"first_login": 1700000104
});
mp.people.increment("new-user-123", {
"login_count": 1
})