Skip to content

Operator onboarding events

Funnel analytics for the operator onboarding flow (/operator/onboarding), emitted from the web frontend and landing in the ClickHouse vf.events table.

Pipeline

OnboardingFlow.tsx
  → track(event, properties)          frontend/src/lib/analytics.ts
  → POST /api/track  (sendBeacon)      survives page unload
  → captureEvent()                     api/src/analytics.ts
  → chInsert("events", …)              api/src/clickhouse.ts
  → vf.events                          ClickHouse (partitioned by env + month)

track() is fire-and-forget: it never throws and never blocks the UI. The server resolves actor identity (actor_type, actor_id, distinct_id) and the active feature-flag snapshot from the session cookie — the client only sends the event name, properties, session_id, and page_path.

For an onboarding operator every row carries:

ColumnValue
actor_typeoperator
actor_idthe operator's numeric id
distinct_ido<id>
event_nameone of the names below
page_path/operator/onboarding
propertiesJSON, per the tables below

Event catalog

The flow is a 2-step wizard (Step 1 = company info, Step 2 = brand profile), preceded by the /operator/signin page. Events are fired from OperatorSignIn.tsx (sign-in) and OnboardingFlow.tsx (the wizard).

operator_onboarding_signin_viewed

Fired once per browser session when the operator sign-in page (/operator/signin) mounts — the true top of the funnel, before the operator is authenticated. Guarded by a useRef so a re-render does not double-count it. Note: this row has no actor_id (the operator isn't signed in yet); join the funnel on session_id.

PropertyTypeNotes
has_email_prefillbooleantrue when arriving via the ?email= recovery link.

operator_onboarding_started

Fired once per browser session when the onboarding flow first mounts with an authenticated operator. Guarded by a useRef so a re-render or a resumed session does not double-count it.

PropertyTypeNotes
resume_step1 | 2Step the operator resumed on (2 if legal_name is saved).

operator_onboarding_step_viewed

Fired every time a step is rendered — on first mount (for the resume step) and on each forward transition. Use it to measure per-step reach and drop-off.

PropertyTypeNotes
step1 | 2The step now visible to the operator.

operator_onboarding_step_submitted

Fired when a step's "Continue" action resolves — both on success and on a persistence error. The success flag is the funnel's primary conversion signal per step; error captures the parsed API message on failure.

PropertyTypeNotes
step1 | 2Step being submitted.
successbooleantrue if the API write succeeded.
errorstringPresent only when success: false — parsed message.

operator_onboarding_asset_uploaded

Fired when a Step 2 logo/cover background upload resolves — on success and on failure. Uploads start the moment a file is picked (not on Save), so this event measures the reliability of the Sanity (with S3 fallback) image pipeline independently of the step-submit conversion.

PropertyTypeNotes
kind"logo" | "cover"Which asset was uploaded.
successbooleantrue if the upload stored.
errorstringPresent only when success: false.

operator_onboarding_completed

Fired immediately after Step 2 persists successfully, just before the redirect to /operator/campaigns. Marks a fully onboarded operator.

PropertyTypeNotes
No props.

Funnel shape

operator_onboarding_signin_viewed
  → operator_onboarding_started
  → operator_onboarding_step_viewed { step: 1 }
  → operator_onboarding_step_submitted { step: 1, success: true }
  → operator_onboarding_step_viewed { step: 2 }
  → (operator_onboarding_asset_uploaded { kind: logo|cover, success })
  → operator_onboarding_step_submitted { step: 2, success: true }
  → operator_onboarding_completed

The Grafana view of this funnel lives in the operations repo at clusters/prod/monitoring/dashboards/verifluence-operator-onboarding.json (UID vf-operator-onboarding). See docs/engineering/monitoring/dashboards.md.

A resumed session starts at step: 2; a Step 1 failure repeats step_submitted { step: 1, success: false } until it succeeds.

Useful queries

sql
-- Onboarding funnel (last 30 days), one row per stage
SELECT event_name, count() AS n
FROM vf.events
WHERE env = 'prod'
  AND event_name LIKE 'operator_onboarding_%'
  AND occurred_at >= now() - INTERVAL 30 DAY
GROUP BY event_name
ORDER BY n DESC;

-- Step completion rate (started → completed)
SELECT
  countIf(event_name = 'operator_onboarding_started')   AS started,
  countIf(event_name = 'operator_onboarding_completed') AS completed,
  round(completed / started, 3)                          AS conversion
FROM vf.events
WHERE env = 'prod'
  AND event_name LIKE 'operator_onboarding_%'
  AND occurred_at >= now() - INTERVAL 30 DAY;

-- Step submit failures, with the surfaced error
SELECT
  JSONExtractInt(properties, 'step') AS step,
  JSONExtractString(properties, 'error') AS error,
  count() AS n
FROM vf.events
WHERE env = 'prod'
  AND event_name = 'operator_onboarding_step_submitted'
  AND JSONExtractBool(properties, 'success') = 0
  AND occurred_at >= now() - INTERVAL 30 DAY
GROUP BY step, error
ORDER BY n DESC;

Adding a new onboarding event

  1. Call track("operator_onboarding_<name>", { … }) at the call site in OnboardingFlow.tsx (or a step component).
  2. Add a row to the Event catalog above with its properties.
  3. No schema change is needed — vf.events stores properties as JSON, so new events and props flow through without a migration.

Verifluence Documentation