Developer guide · v1.3 · Interactive
v1.3 correction — read first
Earlier circulation wrote a raw channel into acquisition_source. Do not do that. The raw, lossless value belongs in acquisition_channel; acquisition_source may contain only an enum from the committed v1.4.0 rollup.
acquisition_source
acquisition_channel
Raw first-touch source such as instagram or whatsapp. Write with $set_once.
instagram
whatsapp
$set_once
Canonical enum bucket only. Never a raw channel string. Write with $set_once.
marketing_affiliate_id
Affiliate code from campaign, for affiliate links only. MAP join key.
campaign
Interactive — §3
This is a real example AirBridge link. Every parameter maps to something in Mixpanel — or explicitly doesn't. Click one to see the rule.
All optional link-derived event props use the exact mx_* names from the param_map in mixxi_attribution_enums.json: mx_channel, mx_placement, mx_phase, mx_episode, mx_recipe, mx_asset, mx_cta, mx_team, mx_funding, mx_ad_platform, mx_persona, mx_layer, mx_aff. MMRP's Channels tab and readout query these exact names — a prop written under any other name will exist in Mixpanel but be invisible to every MMRP surface. The profile keys are acquisition_channel, acquisition_source, marketing_affiliate_id, and airbridge_id. acquisition_source is enum-only.
mx_*
param_map
mixxi_attribution_enums.json
mx_channel
mx_placement
mx_phase
mx_episode
mx_recipe
mx_asset
mx_cta
mx_team
mx_funding
mx_ad_platform
mx_persona
mx_layer
mx_aff
airbridge_id
§4 — latest-touch event context, "read carefully"
The rule that's easy to get backwards: profile fields never change after first touch, but the mx_* super props update on every new tagged open — and organic opens don't clear them. Step through three sessions for the same user to see it happen.
§1
The app owns everything inside the device. The server-side join is MMRP's — but it only works if the app sets the join keys, which is why this is a developer spec.
go.mixxi.ai
abr.ge
channel
distinct_id
signup_complete
live_watch_anchor
§2 — read this first, most bugs live here
Four rules, in this order. Breaking the order causes silent attribution loss — no error, just a missing join.
Consent gate is absolute (DPDP). No SDK initializes, no storage is written, and no event fires before affirmative consent. Deny-by-default.
Mixpanel initializes before attribution work. A distinct_id must exist. Order: consent → Mixpanel init → AirBridge init → read link → set props.
Handle deferred deep links. New installs receive params through AirBridge's deferred callback; existing installs use the immediate handler. Handle both.
Profile attribution is first-touch immutable. acquisition_channel, acquisition_source, and affiliate credit use $set_once. A wrong enum can never be corrected for that user.
§5 — app sequencing
Use the real SDK method names per platform. The order and property names are the contract.
// Phase 1: before code-freeze, write raw channel only to acquisition_channel. if (!userHasConsented()) return; mixpanel.init(MIXPANEL_TOKEN); mixpanel.identify(stableDistinctId()); airbridge.init(AIRBRIDGE_APP_TOKEN); airbridge.onDeferredDeeplink(params => applyAttribution(params)); airbridge.onDeeplink(params => applyAttribution(params)); function applyAttribution(params) { mixpanel.people.setOnce({ acquisition_channel: params.channel }); // Phase 2: also set acquisition_source from the v1.4.0 JSON rollup. // Never write a raw channel into acquisition_source. mixpanel.people.set({ airbridge_id: airbridge.getAirbridgeId() }); if (isAffiliate(params)) { mixpanel.people.setOnce({ marketing_affiliate_id: params.campaign }); } mixpanel.register({ mx_channel: params.channel, mx_phase, mx_episode, mx_recipe, mx_asset, mx_placement, mx_funding, mx_ad_platform, mx_persona, mx_layer, mx_team, mx_cta, ...(isAffiliate(params) && { mx_aff: params.campaign }) }); // overwrite on tagged opens; omit absent params } mixpanel.track("signup_complete", { new_otp_unique_account: <bool> }); // NOT account_created
§5
The AirBridge server postback hits MMRP at /api/ingestion/airbridge/*. MMRP joins that postback to the Mixpanel funnel using airbridge_id (and marketing_affiliate_id for affiliate credit). The join is deterministic-only — if the app didn't set these keys, the postback and the funnel can't be joined and the install looks organic.
/api/ingestion/airbridge/*
Affiliate credit follows a no-clawback ledger: money is only released on met conditions, never reversed — so a missing join key means an affiliate is silently underpaid. That's why §2/§3 are strict.
§6 — must not get wrong
account_created
§7 — non-negotiable
new_otp_unique_account
episode_id
§9
Use a clean device (no prior install), per platform and per environment. Events are not backfillable.
RISING-E0-TESTONLY
instagram / phase1_tease__ep01_discovery / trend__testasset_01 / funding=organic
Production is behind. Verify all three environments before Episode 0 / Discovery airs on 31 July 2026.
§10 — do not hardcode against these
Rollup-in-JSON. Confirm the v1.4.0 file encodes the rollup ruleset as data, not only enum members. The app team must be on v1.4.0, not v1.0.0.
Affiliate-code postback field — partner vs sub_id. Console-defined and unconfirmed. campaign remains the stable in-app source; recommendation is a dedicated sub_id.
partner
sub_id
go.mixxi.ai. Cosmetic for deferred attribution; direct Universal/App Links are a separate entitlement change.
vote_cast. Do not build client-side. Preferred path: Unravel backend emits it from validated Kafka votes.
vote_cast
v1.3 consolidation
v1.3 supersedes v1.0–v1.2 and resolves the old rollup guidance against the committed v1.4.0 contract.
Rollup corrected. Affiliate presence wins; paid traffic keys off ad_platform, not channel. Funding is only organic or sponsorship_funded.
ad_platform
organic
sponsorship_funded
Two profile props. Write raw acquisition_channel and enum acquisition_source, both first-touch and $set_once.
Code-freeze sequencing added. Phase 1 writes raw-only to acquisition_channel; Phase 2 adds the JSON-driven enum rollup.
Launch context corrected. Episode 0 / Discovery airs 31 July. Verify in Production because events are not backfillable.