Skip to main content

Mixpanel

Server Source code Package

Server-side event delivery to Mixpanel for product analytics, people profiles, and group analytics via the official mixpanel Node.js SDK.

Where this fits

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
import { startFlow } from '@walkeros/collector';
import { destinationMixpanel } from '@walkeros/server-destination-mixpanel';

await startFlow({
destinations: {
  mixpanel: {
    code: 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

PropertyTypeDescriptionMore
apiKey*stringYour Mixpanel project token. Find it in Project Settings > Access Keys. Passed as the first argument to Mixpanel.init().
secretstringAPI secret for the /import endpoint (historical data). Required when useImport is true.
hoststringMixpanel API host. Default: 'api.mixpanel.com' (US). Use 'api-eu.mixpanel.com' (EU) or 'api-in.mixpanel.com' (India).
protocolstringProtocol for API requests. Default: 'https'.
keepAlivebooleanReuse HTTP connections. Default: true.
geolocatebooleanParse IP for geolocation. Default: false. Server IP caveat: all users map to server location unless $ip is overridden.
debugbooleanEnable SDK debug logging. Default: false.
verbosebooleanEnable verbose request logging. Default: false.
testbooleanEnable dry-run mode. Default: false.
useImportbooleanUse /import endpoint instead of /track. Accepts events of any age (no 5-day limit). Requires secret for authentication.
identifyanywalkerOS mapping value resolving to { distinctId, alias? }. distinctId is passed as distinct_id on every SDK call.
includeanyEvent data sections to flatten into track() properties. Example: ['data', 'globals']. Sections are prefixed (data_, globals_, etc.).
* Required fields

Mapping

Per-event rules under config.mapping. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.

PropertyTypeDescriptionMore
identifyanyPer-event identity mapping. Resolves to { distinctId, alias? }. distinctId is passed as distinct_id to all SDK calls.
peopleanyPer-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.
groupanyPer-event group association. Resolves to { key, id }. The group key/id is added as a track() property.
groupProfileanyPer-event group profile operations. Resolves to { key, id, set?, set_once?, union?, remove?, unset?, delete? }. Fires mp.groups.* calls.
useImportanyPer-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.

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"
  }
}
Mapping
{
  "settings": {
    "identify": {
      "map": {
        "distinctId": "data.user_id",
        "alias": "data.anon_id"
      }
    }
  }
}
Out
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.

Event
{
  "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"
  }
}
Mapping
{
  "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"
          ]
        }
      }
    }
  }
}
Out
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.

Event
{
  "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"
  }
}
Mapping
{
  "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"
          }
        }
      }
    }
  }
}
Out
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.

Event
{
  "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"
  }
}
Out
mp.track("product view", {
  "distinct_id": "us3r"
})

Historical import

Setting useImport routes the event through mp.import for backfilling historical Mixpanel data.

Event
{
  "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"
  }
}
Out
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.

Event
{
  "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"
  }
}
Mapping
{
  "settings": {
    "identify": {
      "map": {
        "distinctId": "data.user_id"
      }
    }
  }
}
Out
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.

Event
{
  "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"
  }
}
Mapping
{
  "settings": {
    "group": {
      "map": {
        "key": {
          "value": "company_id"
        },
        "id": "data.company_id"
      }
    }
  }
}
Out
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.

Event
{
  "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"
  }
}
Out
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.

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"
  }
}
Mapping
{
  "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
            }
          }
        }
      }
    }
  }
}
Out
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
})
💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)