The Mixpanel event tracking spec for the onboarding-v2 flow. Product
calls this flow "Onboarding v1.5"; the code lives at
cur8-mob/src/screens/onboarding-v2/. They are the same
thing.
This doc covers Mixpanel events and people-profile traits
only. Customer.io, Avo, Google Ads, session replay and
compliance are out of scope here. The older
cur8-mob/ANALYTICS_ARCHITECTURE.md (v3.2) is kept as a
reference for the wider design, including the deferred
navigation-listener and abandonment-tracking work.
onboarding-v2 currently ships with no analytics
calls. This doc is the spec for what to add, screen by screen.
The screen inventory is taken from
cur8-mob/src/navigation/onboardingV2.tsx (the
OnboardingV2StackParamList), which is the source of
truth.
1. What the events answer
| Question | Events |
|---|---|
| Where in onboarding do users drop? | every _pageViewed, each _completed,
cur8:onboarding_completed |
| Quiz pass rate | cur8:onboardingQuiz_passed vs
cur8:onboardingQuiz_failed |
| Accreditation type mix | cur8:onboardingAccreditation_completed.certification |
| Ineligible / restricted-investor rate | cur8:onboardingAccreditationIneligible_pageViewed,
_certificationSelected.certification |
| KYC upload success rate | cur8:onboardingVerification_documentUploaded vs
_uploadFailed |
| Attribution source mix | profile attribution_source, filtered on
cur8:onboarding_completed |
| Personalisation inputs | cur8:onboardingMandatoryQuestion_answered (goals,
portfolio size, timeframe) |
| Quiz failure stage (first-time vs locked-out) | cur8:onboardingQuiz_failed.attempt_band |
| KYC completion by identity branch | cur8:onboardingVerification_completed.identity_status |
| Address-entry friction | cur8:onboardingBasicInfo_completed.is_address_autocompleted |
Note: profile traits (section 5) attach to every event automatically as Mixpanel "User properties". Any funnel or report can be sliced by them without the event carrying the value itself.
2. How events are wired
Every event goes through the shared
@ifgengineering/client-analytics package, the same path
every other cur8-mob event already uses. Screens never call
mixpanel.track directly.
A screen gets a fire function from the useAnalytics hook
and invokes it. The hook returns a promise, so callers
await it. Wrap the call so a failure never blocks the user,
matching existing screens such as home.tsx.
There are two shapes.
2.1 Page views
Page views reuse the generic pageView event key. Add the
event-name string to the cur8PageViewEvents union in
packages/client-analytics/src/types.ts, then fire it. The
pageView handler attaches app and
channel for you, so the call site only passes
eventName.
const firePageView = useAnalytics("pageView");
// fire on screen focus:
(await firePageView)({ eventName: "cur8:onboardingPersonalInfo_pageViewed" });
For the three paginated routes (MandatoryQuestion,
AccreditationQuestionnaire, QuizQuestion) one
route instance serves every question, so the screen does not re-focus
per question. Fire those _pageViewed events from a
useEffect([questionIndex]), not from focus.
2.2 Action and outcome events
Action and outcome events get their own entry:
- In
types.ts, add the key to theEventunion and a<Name>Propsinterface. - In
packages/client-analytics/src/index.ts, add acasethat callstrack("cur8:onboarding…_…", { ...args, app: APP_NAME }). - Fire it from the screen:
const fireCertificationSelected = useAnalytics("onboardingCertificationSelected");
(await fireCertificationSelected)({ certification: "HIGH_NET_WORTH" });
People-profile traits are set inside the same case via
mixpanel.people?.set, the way the nps_set case
already does it (note the optional chaining, the codebase uses it). The
fire function is the only thing a screen imports.
track also forwards every event to Customer.io. That
fan-out is unconditional in the current package code. It is out of scope
for this doc, but be aware new onboarding events reach Customer.io
too.
3. Naming
- Events:
cur8:onboarding<Screen>_<action>.cur8:is the team namespace,<Screen>is PascalCase,<action>is a past-tense lowerCamelCase verb (_pageViewed,_completed,_passed,_failed,_documentUploaded,_answered). Module-level events drop the screen:cur8:onboarding_completed. - This matches the existing onboarding events already in Mixpanel
(
cur8:onboardingQuiz_pageViewed,cur8:onboardingBasicInfoPersonal_pageViewed). - Event properties:
snake_case. Booleans prefixis_/has_. Counts suffix_count. Timestamps suffix_at(ISO 8601). Banded values suffix_band.
Note: cur8:onboardingQuiz_pageViewed
already exists as a v1 event. This spec assumes v2 replaces v1, so it
reuses the clean names. If v1 and v2 ever run side by side, reusing a
name merges the two in Mixpanel. Namespace the v2 events in that
case.
4. Event taxonomy
Re-derived from the registered routes in
onboardingV2.tsx. Every screen fires one
_pageViewed on focus. Each module fires one
_completed on its final screen, carrying that module's
captured outputs as properties. No raw PII goes on any event
(age_band, not date of birth; source_of_funds
option keys, not free text).
_pageViewed events tell you where a user stalled, not
why. Within the lean event set there is no "saw the screen but did not
act" signal except where an explicit action event exists
(_answered, _certificationSelected).
4.1 Entry and Mandatory Questionnaire
| Event | Properties | Trigger |
|---|---|---|
cur8:onboardingWelcomeBridge_pageViewed |
none | Welcome bridge screen appears. Funnel entry. |
cur8:onboardingMandatoryQuestion_pageViewed |
question_index |
Each questionnaire question appears (paginated route, fires on index change). |
cur8:onboardingMandatoryQuestion_answered |
question_index, question_id,
answer_id |
User selects an option for a question. Captures the personalisation inputs. |
cur8:onboardingEmailPreferences_pageViewed |
none | Email preferences screen appears. |
cur8:onboardingSocialAttribution_pageViewed |
none | Social attribution screen appears. |
cur8:onboardingMandatoryQuestionnaire_completed |
email_preference, attribution_source |
Social attribution submitted. The questionnaire batch is POSTed at email preferences. Headline funnel anchor. |
email_preference: cur8_capital /
cur8_and_ifg / none.
attribution_source: google /
instagram / tiktok / youtube /
friend_family / podcast / ifg /
other.
Note: the questionnaire is server-driven
(GET /investor/mandatory-questionnaire), so
question_id and answer_id are server
identifiers. The three questions are investment goals, portfolio size
and investment timeframe, but the mapping from question_id
to which one is resolved analysis-side from the stable IDs. The question
count is not guaranteed to be three.
4.2 Basic Info
Seven screens: one explainer, six single-purpose data screens.
| Event | Properties | Trigger |
|---|---|---|
cur8:onboardingBasicInfoExplainer_pageViewed |
none | Explainer appears. |
cur8:onboardingPersonalInfo_pageViewed |
none | Personal info screen appears. |
cur8:onboardingResidentialInfo_pageViewed |
none | Residential info screen appears. |
cur8:onboardingTaxNINumber_pageViewed |
none | NI number screen appears. |
cur8:onboardingTaxResidency_pageViewed |
none | Tax residency screen appears. |
cur8:onboardingTaxSourceOfFunds_pageViewed |
none | Source of funds screen appears. |
cur8:onboardingTaxOccupation_pageViewed |
none | Occupation screen appears. |
cur8:onboardingBasicInfo_completed |
age_band, country,
nationality, is_uk_tax_resident,
source_of_funds, occupation,
is_address_autocompleted |
Occupation screen submitted. Headline funnel anchor. |
age_band: 18-24 / 25-34 /
35-44 / 45-54 / 55-64 /
65+. Derived from the date of birth held in
OnboardingV2Context (captured back on
PersonalInfo); the raw DOB never leaves the client.
source_of_funds is an array of selected option keys.
occupation is the option key, not the display label.
is_address_autocompleted is true when the user
picked a Google Places suggestion on the address screen rather than
typing the address by hand; the flag is carried through
OnboardingV2Context from ResidentialInfo.
4.3 Accreditation
Code folder: eligibility/. The flow branches on the
chosen certification: self-certified and high-net-worth users continue
through the questionnaire and declaration. US residents are offered a
US-accredited option instead. Users who pick "none apply"
(NONE_APPLY) route to the not-eligible screen.
| Event | Properties | Trigger |
|---|---|---|
cur8:onboardingAccreditationExplainer_pageViewed |
none | Explainer appears. |
cur8:onboardingCertification_pageViewed |
none | Certification screen appears. |
cur8:onboardingAccreditation_certificationSelected |
certification |
User confirms a certification. Sets the
investor_certification profile trait. |
cur8:onboardingAccreditationQuestionnaire_pageViewed |
question_index, profile_type |
Each questionnaire question appears (paginated route, fires on index
change). profile_type is the route param; self-certified
and HNW users see different questions. |
cur8:onboardingAccreditationIneligible_pageViewed |
profile_type |
Ineligible screen appears. A genuine drop-off point. |
cur8:onboardingAccreditationNotEligible_pageViewed |
none | Not-eligible screen appears. |
cur8:onboardingUSAccreditationReason_pageViewed |
none | US accreditation reason screen appears. |
cur8:onboardingDeclaration_pageViewed |
none | Declaration screen appears (four risk-acknowledgement checkboxes). |
cur8:onboardingAccreditation_completed |
certification, yes_answer_count |
Declaration submitted. Headline funnel anchor. |
certification: SELF_CERTIFIED /
HIGH_NET_WORTH / US_ACCREDITED /
NONE_APPLY. NONE_APPLY appears only on
_certificationSelected; those users route to the
not-eligible screen and never reach _completed.
profile_type: SELF_CERTIFIED /
HIGH_NET_WORTH / US_ACCREDITED.
yes_answer_count is how many questionnaire questions were
answered Yes (the questionnaire content itself is not captured).
4.4 Quiz
FCA appropriateness check. On submit the quiz navigates to a
dedicated QuizResult screen, which receives
failed and an optional blockedUntil and shows
a live countdown when the user is rate-limited.
| Event | Properties | Trigger |
|---|---|---|
cur8:onboardingQuizExplainer_pageViewed |
none | Explainer appears. |
cur8:onboardingQuiz_pageViewed |
question_index |
Each quiz question appears (paginated route, fires on index change). |
cur8:onboardingQuiz_passed |
none | QuizResult opens with failed=false.
Headline funnel anchor. Sets
quiz_passed_at. |
cur8:onboardingQuiz_failed |
blocked_until, attempt_band |
QuizResult opens with failed=true. Fires
on any non-passing result. |
blocked_until is the ISO 8601 rate-limit timestamp, or
omitted when the user is not rate-limited (the quiz API returns
{ failed, blockedUntil? }). attempt_band is
first_fail / second_or_third_fail /
fourth_plus_fail, derived client-side from
blocked_until against the server's fixed block ladder (fail
1 = no block, fails 2 to 3 = 1-day block, fails 4+ = 7-day block). It
separates first-time failures from hard-stuck repeat users.
Note: the quiz API returns only pass/fail and the
rate-limit window, not a score, and the client cannot grade the answers.
attempt_band is the closest available proxy. The clean fix
is for the backend to return attemptCount on the quiz
response (it already computes it); track that as a follow-up.
4.5 Verification (KYC)
The verification flow branches on the user's identity status: most users go straight to document upload, some are routed to an identity scan, blacklisted users hit the blocked screen.
| Event | Properties | Trigger |
|---|---|---|
cur8:onboardingVerificationExplainer_pageViewed |
none | Verification explainer appears. |
cur8:onboardingVerificationIntro_pageViewed |
none | Requirements intro screen appears. |
cur8:onboardingIdentityScan_pageViewed |
none | Identity scan screen appears (users who need an ID scan). |
cur8:onboardingVerificationBlocked_pageViewed |
none | Blocked screen appears (blacklisted users). |
cur8:onboardingProofOfAddress_pageViewed |
none | Proof of address screen appears. |
cur8:onboardingSourceOfFunds_pageViewed |
none | Source of funds screen appears. |
cur8:onboardingVerificationStatus_pageViewed |
none | Verification status screen appears. |
cur8:onboardingVerification_documentUploaded |
doc_type, file_count |
The document set for one doc_type uploads successfully.
Uploads are batched at the CTA, not per file. No filenames. |
cur8:onboardingVerification_uploadFailed |
doc_type, failure_reason |
A document upload batch errors. |
cur8:onboardingVerification_completed |
document_count, identity_status |
Verification status submitted (descriptions PUT, KYC marked submitted). Headline funnel anchor. |
doc_type: proof_of_address /
source_of_funds. identity_status:
needs_scan / grey_list. The client-side KYC
branch hint; it is never sent to the backend, so this event is the only
place the grey-list versus standard-KYC split is observable.
failure_reason: file_too_large /
unsupported_format / network_error /
server_error / other. Map the upload
pipeline's real error to this closed set at emission; never pass a raw
exception string.
4.6 Completion and cross-flow
| Event | Properties | Trigger |
|---|---|---|
cur8:onboardingCompletion_pageViewed |
none | Completion splash appears. |
cur8:onboarding_completed |
none | POST /onboarding/completion succeeds. Terminal
funnel event. |
cur8:onboarding_backPressed |
from_screen, to_screen |
Back button pressed inside onboarding. |
cur8:onboarding_helpTapped |
screen, help_target |
A help, info or support affordance is tapped. |
5. People-profile traits
User state lives on the Mixpanel people-profile, not on events. Each
trait is set with mixpanel.people?.set inside the event
handler named below, alongside the track call.
| Trait | Set by |
|---|---|
email_preference |
cur8:onboardingMandatoryQuestionnaire_completed |
attribution_source |
cur8:onboardingMandatoryQuestionnaire_completed |
age_band |
cur8:onboardingBasicInfo_completed |
country |
cur8:onboardingBasicInfo_completed |
nationality |
cur8:onboardingBasicInfo_completed |
is_uk_tax_resident |
cur8:onboardingBasicInfo_completed |
occupation |
cur8:onboardingBasicInfo_completed |
investor_certification |
cur8:onboardingAccreditation_certificationSelected |
quiz_passed_at |
cur8:onboardingQuiz_passed |
kyc_status |
cur8:onboardingVerification_completed |
onboarding_completed_at |
cur8:onboarding_completed |
Note: onboarding-v2 runs post-signup. The user is
already identified at signup (email-keyed, via
safeMixpanelIdentify in the analytics package).
onboarding-v2 fires no identify, alias or
reset calls. Mixpanel's mobile SDK auto-properties
($os, $app_version_string,
$device, and so on) are free; do not duplicate them.
6. Headline funnel
| Step | Event |
|---|---|
| 1 | cur8:onboardingWelcomeBridge_pageViewed |
| 2 | cur8:onboardingMandatoryQuestionnaire_completed |
| 3 | cur8:onboardingBasicInfo_completed |
| 4 | cur8:onboardingAccreditation_completed |
| 5 | cur8:onboardingQuiz_passed |
| 6 | cur8:onboardingVerification_completed |
| 7 | cur8:onboarding_completed |
Slice by profile properties country,
attribution_source, investor_certification,
email_preference. Per-screen _pageViewed
events give the within-module drop-off.
Caution: the funnel hides quiz failure. A user who
fails the quiz and never passes simply does not reach step 5, with no
signal distinguishing a failed quiz from an abandoned explainer. Track
quiz fail rate separately as cur8:onboardingQuiz_failed
count against cur8:onboardingQuiz_passed. The
restricted-investor branch (step 4) is similar:
UNACCREDITED users who stop at the ineligible screen need
cur8:onboardingAccreditationIneligible_pageViewed, not the
funnel, to be counted.
7. Not covered here
Deliberately cut from the v3.2 architecture doc for this simplified spec:
- Abandonment events (
_screenAbandoned,_abandoned,_attemptAbandoned,_docAbandoned) and the navigation-state listener /onboarding_session_idsubsystem they need. Drop-off is read from where users stop in the_pageViewedfunnel. active_*_mstiming properties. Mixpanel derives time-between-steps from event timestamps; foreground-pause timing has no precedent in the codebase.- Per-event metadata (
$insert_id,experiment_variant,flag_resolution) and theexperiment_exposedevent. - Customer.io, Avo, Google Ads, session replay, PII rules and compliance.
See cur8-mob/ANALYTICS_ARCHITECTURE.md for the fuller
v3.2 design if any of the above is revived.