Analytics · engineers · PMs
Mixpanel join key is distinct_id
Funnels with a 1% conversion because every hit is a new distinct_id look like a product disaster. They are usually an SDK init disaster.
A bare array, token inside properties
The track endpoint posts a JSON array, not an envelope. There is no top-level api_key. The project token is properties.token on every event. event is required. properties is required because the token lives there. Pixellint: vendor.mixpanel.body.event.missing, .properties.missing, .properties.token.missing. A payload shaped like PostHog or Amplitude will not match this pack. A Mixpanel-shaped payload with no token is an error.
Import (/import) is a different path with a different auth story. Read the endpoint you POST to. A 2016 blog that says time is seconds may be describing import, engage, or an old track query-string mode. The pack matches mixpanel.com /track and /import against the array shape.
[{
"event": "Signed Up",
"properties": {
"token": "PROJECT_TOKEN",
"distinct_id": "user-123",
"$insert_id": "evt-1",
"time": 1770000000
}
}]
distinct_id empty is a hole. $insert_id empty is a retry bomb.
properties.distinct_id identifies the user. Without it the event is not tied to a person. Present and empty is flagged: vendor.mixpanel.body.properties.distinct_id.empty. Mixing email as distinct_id on the server and a UUID on the client splits the profile. Pick one canonical id and alias carefully. Engage/identify must reuse the same distinct_id the track calls used.
properties.$insert_id is how Mixpanel drops duplicates so a retried request is not counted twice. Present and empty is flagged: vendor.mixpanel.body.properties.$insert_id.empty. Mint it once, persist it, replay it. A missing $insert_id on a flaky network is how Sign Up counts twice.
pixellint validate json @mixpanel.json --rulepack vendor/mixpanel
Identify is not track
Profile properties (people) are not event properties. Setting plan=pro on the user and never sending an event named subscribed will confuse PMs who live in Insights. Track carries the verb. People carries the nouns. Both need the same distinct_id. Alias is a one-way merge in Mixpanel's model if you use it that way; reversing it is a support ticket, not a second API call you invent.
Group keys and group profiles are a third identity. Shipping company_id only on some events is how B2B funnels look empty for half the accounts.
Time depends on the endpoint
Track's properties.time is epoch seconds in Mixpanel's current track docs for that field when you send it. Import has its own rules. Do not send ISO 8601 here because PostHog wanted it. Do not send 13-digit milliseconds because Amplitude wanted it. Read the endpoint. A blog from 2016 is not the contract.
Omit time and Mixpanel uses received-at. Live SDK traffic can omit it. A backfill cannot. The same class of bug as PostHog, opposite type.
PII still exists in product analytics
Mixpanel is product analytics. Still personal data. Hashing emails for Mixpanel the way you do for Meta is optional and breaks people search if you did not mean to. Keep raw email out of query strings. Keep live HARs out of git. The pack flags empty distinct_id and empty $insert_id. It does not hash for you.
Pixellint is not affiliated with Mixpanel. The pack cites the track event reference. Token in properties is the thing people miss when they copy an Amplitude envelope.
Check the artifact
Paste the pixel URL or JSON body into the
playground. Same engine as
pixellint validate. Nothing leaves the tab.