This is the maintained dictionary for events that drive a product funnel, reliability or compliance check, or a regression alert. It deliberately does not inventory every analytics wrapper: most of those events are not load-bearing, and a generated list of method names would not identify the product boundary that owns emission.
The authoritative emitter below is the state or user-action boundary that decides whether an event happened. Analytics-manager methods only shape and send its payload.
- Update this file in the same change when a listed event is renamed, moved, starts or stops emitting, changes a listed property/person property, or changes alert coverage.
- Add an event when a production funnel, reliability/compliance check, or regression alert begins to depend on it. Do not add every analytics method.
- Keep dimensions bounded and content-free. Normalize unknown values to
unknown; never send transcripts, chat text, deletion errors, email, or device names in the properties documented here. - macOS and Windows are separate surfaces. Windows has no BLE stack and cannot emit pairing, connection, or pendant-session events. Never report a blended "desktop" hardware funnel.
- Alert links below point to the checked-in configuration contract. The alerts are not live until their PostHog insight/alert links replace that local link; creating them is a separate production write.
device_vendor is the shared closed dimension:
omi, limitless, plaud, bee, apple, fieldlabs, friend,
neosapien, above, utami, meta, or unknown.
Every Flutter mobile event is stamped centrally with closed app_platform (ios, android, or unknown), semantic app_version, and store app_build. These canonical fields override conflicting call-site values; PostHog SDK-reserved version fields are not the cross-platform contract.
| Event | Owning surface | Emission contract and authoritative emitter | Key properties / person properties | Alert |
|---|---|---|---|---|
Connect Device Page Opened |
Flutter mobile (iOS/Android) | Once when the connect-device page initializes: ConnectDevicePage.initState |
No event or person properties. | Not alerted; funnel denominator for Get Omi Device Clicked. |
Get Omi Device Clicked |
Flutter mobile (iOS/Android) | Before launching the external Omi store URL: openOmiStore |
No event or person properties. | Specified: weekly unique people; not yet provisioned. |
Permissions Interstitial Shown |
Flutter mobile (fresh-install permissions flow; repaired for Android) | Once per mounted interstitial: _PermissionsInterstitialPageState.initState |
No event or person properties. | Not alerted; must be greater than or equal to Permissions Interstitial Completed by platform. |
Permissions Interstitial Completed |
Flutter mobile (iOS/Android) | When the user completes the permission actions and continues: PermissionsInterstitialPage |
No event or person properties. | Not alerted; completion numerator for the shown/completed invariant. |
Device Connected |
Flutter mobile BLE (iOS/Android) | Once for each disconnected/different-device → connected transition, regardless of connection path: DeviceProvider.setConnectedDevice |
Event: closed device_vendor, closed hardware_family, enum-name type; hardware_id only when the device exposes a manufacturer serial, with hardware_id_kind and hardware_id_stable; the BLE transport_device_id is explicitly labeled platform_dependent. Person: device_vendor, hardware_family. |
Specified: weekly unique people and connected/disconnected ratio; not yet provisioned. |
Device Connected |
macOS desktop BLE | Once for each successful DeviceProvider.connect happy path after the session becomes active: DeviceProvider.connect via AnalyticsManager.deviceConnected |
Event: closed device_vendor, enum device_type. Person: device_vendor. No free-text device names. |
Same shared weekly alert / connected-disconnected ratio contracts; report macOS separately from mobile. |
Device Disconnected |
Flutter mobile BLE (iOS/Android) | On the BLE disconnect callback after state cleanup: DeviceProvider.onDeviceDisconnected |
No event or person properties. | Specified: weekly unique people and connected/disconnected ratio; not yet provisioned. |
Device Disconnected |
macOS desktop BLE | Once per ended active session from DeviceSessionCoordinator.onSessionEnded (disconnect or active unpair); unpair of an already-disconnected device emits nothing: DeviceProvider / AnalyticsManager.deviceDisconnected |
No event or person properties. | Same shared weekly alert / connected-disconnected ratio contracts; report macOS separately from mobile. |
Device Paired |
Flutter mobile BLE (iOS/Android) | First successful connection to a device.id for the current local uid, deduped in local preferences: DeviceProvider.setConnectedDevice |
Event: closed device_vendor, closed hardware_family, enum-name type; hardware_id only when the device exposes a manufacturer serial, with hardware_id_kind and hardware_id_stable; the BLE transport_device_id is explicitly labeled platform_dependent. Person: has_paired_device=true, ISO-8601 first_paired_at, device_vendor, hardware_family. |
Specified: weekly unique people; not yet provisioned. |
Device Paired |
macOS desktop BLE | After a successful connection, once per new device ID in local preferences; a previously persisted device is seeded so an upgrade/reconnect is not mislabeled as a new pair: DeviceProvider.recordPairingAnalytics |
Event: closed device_vendor, enum device_type, normalized model, boolean is_first_pair. Person: has_paired_device=true, paired_device_type, device_vendor, ISO-8601 first_paired_at when known. |
Same shared weekly alert contract; report macOS separately from mobile. |
App Session Started |
Flutter mobile (iOS/Android) | Once at cold launch and once for each background-to-foreground transition; repeated resume callbacks without a background transition are ignored: AppSessionTelemetry |
Fresh opaque app_session_id; closed start_kind (coldStart or foreground). No person properties. |
Not alerted; mobile session/retention denominator. |
App Launched |
Windows desktop | Once when the authenticated, onboarded main app shell mounts: AppShellInner, through trackEvent |
$lib=omi-windows, $os=Windows, platform=windows, and authenticated distinct_id. |
Not alerted; Windows session/retention denominator. |
Onboarding How Did You Hear |
Windows desktop | When the Windows onboarding source is selected: Onboarding.handleHowDidYouHear, through trackHowDidYouHear |
Event: allowlisted source, boolean is_referral; every Windows event also carries $lib=omi-windows, $os=Windows, platform=windows, and distinct_id. |
Not alerted; this is the Windows transport canary. It is not a hardware-conversion event. |
Onboarding Step Completed |
macOS desktop (Second Brain onboarding) | Once on every exit from a live SBOnboardingModel.Step, including skips and already-granted permission jumps: SBOnboardingModel.advance / recordStepExit. |
Bounded step (enum case name), index, non-negative elapsed_ms, boolean skipped, closed exit_reason (answered / skipped / auto_granted); permission steps also send closed permission and boolean granted. skipped is true for both a user Skip and an auto-granted jump; a skip-rate must filter exit_reason=skipped, not skipped=true. Never names, roles, or page copy. |
Not alerted; denominator for the admin macOS onboarding funnel (web/admin/lib/onboarding-funnel.ts). |
Onboarding Completed |
macOS desktop (Second Brain onboarding) | Once when onboarding hands off to the app, on both finish and skip-the-rest: SBOnboardingModel.finishOnboardingHandoff. |
No event properties. | Not alerted; terminal row of the admin macOS onboarding funnel. |
Hardware Purchased |
External order-fulfilment system (not present in this repository) | Not emitted today. Emit only after fulfilment and email/order identity has been authoritatively reconciled to an Omi uid, using the backend's fail-open emit_posthog_event. Checkout intent or a client redirect is not fulfilment. |
Planned event: order_id, normalized sku, device_vendor=omi, normalized channel, ISO country; distinct_id=uid. No person properties. |
Not alerted until a real fulfilment emitter exists and has two completed weeks. |
Encoded Audio Duration Measured is emitted by the live listen receiver at session teardown from authoritative decoded mono PCM bytes and negotiated sample rate. It carries recording/conversation identity, codec, decoded bytes, and audio duration; malformed frames are excluded.
Speech Positive Duration Measured is emitted by the live listen receiver at session teardown from the server VAD's cumulative speech clock. It carries recording/conversation identity, seconds, VAD mode, and the closed server_vad measurement source.
Diarization Completed is emitted after the live transcript loop drains speaker work and durably flushes assignments. It carries recording/conversation identity, distinct STT speaker count, and the closed stt_provider source; no labels or transcript content.
| Event | Owning surface | Emission contract and authoritative emitter | Key properties / person properties | Alert |
|---|---|---|---|---|
Recording Started |
Flutter mobile capture (phone mic and pendant; live and Transcribe Later) | Exactly once after the native/BLE capture path confirms it started producing audio: RecordingLifecycleTelemetry, owned by CaptureController |
Client-minted recording_id UUID and closed `recording_source=phone_mic_live |
phone_mic_batch |
Recording Completed |
Flutter mobile capture | Exactly once when a started phone-mic or pendant capture stops, including controller disposal: RecordingLifecycleTelemetry |
Start-event fields plus non-negative duration_seconds and closed `reason=user_stopped |
device_disconnected |
Recording Start Failed |
Flutter mobile capture | Exactly once when an ID was prepared but capture could not start; no Recording Started event is emitted for that attempt: RecordingLifecycleTelemetry |
recording_id, recording_source, and closed `failure_class=permission_denied |
capture_unavailable |
Recording Upload Started |
Flutter mobile offline-audio queue (iOS/Android) | Once after account-cutover and rate-limit admission, immediately before every /v2/sync-local-files request: SyncUploadGate.upload |
Opaque upload_attempt_id; recording_id when the WAL is already bound to a conversation; file_count, total_bytes, boolean claims_live_capture, and closed upload_source=offline_audio_queue. No filenames or paths. |
Not alerted; denominator for upload completion/failure rate and latency. |
Recording Upload Completed |
Flutter mobile offline-audio queue (iOS/Android) | Exactly once when an admitted upload request returns HTTP 200 (completed) or 202 (accepted): SyncUploadGate.upload |
Start-event fields plus duration_seconds and closed `result=accepted |
completed`. |
Recording Upload Failed |
Flutter mobile offline-audio queue (iOS/Android) | Exactly once when an admitted upload request throws; admission blocks do not masquerade as network attempts: SyncUploadGate.upload |
Start-event fields plus duration_seconds and closed `failure_class=rate_limited |
timeout |
Transcript Started |
Backend /v4/listen provider lane |
Once, when an Omi-owned live STT attempt receives its first nontrivial audio frame: LiveSTTAttempt |
recording_id, conversation_id when allocated, closed transcription_source, bounded stt_provider, configured stt_model, transcript_language, and bounded app_platform. uid is only the PostHog distinct_id. No transcript content. |
Not alerted; live transcription denominator. |
Transcript Completed |
Backend /v4/listen provider lane |
Exactly once when a nonempty transcript is successfully delivered to the client WebSocket: LiveSTTAttempt.finish |
Start-event fields plus non-negative duration_seconds and bounded terminal phase. |
Not alerted; terminal success and first-transcript latency joined by recording_id. |
Transcript Failed |
Backend /v4/listen provider lane |
Exactly once when an accepted live STT attempt terminates on a provider or pipeline failure before successful delivery: ListenSessionRuntime._finish_live_transcription |
Start-event fields plus non-negative duration_seconds and bounded failure phase. Raw provider errors are excluded. |
Not alerted; terminal failure numerator joined by recording_id. |
Transcript Cancelled |
Backend /v4/listen provider lane |
Exactly once when accepted audio ends normally before any nonempty transcript is delivered: ListenSessionRuntime._finish_live_transcription |
Start-event fields plus non-negative duration_seconds and phase=teardown. |
Not alerted; separate from failures so silence/user cancellation does not inflate failure rate. |
Speaker Identity Proposed |
Backend /v4/listen speaker identification |
Once for each speaker-label suggestion sent to the capture client: ListenSessionRuntime.emit_speaker_suggestion |
recording_id, conversation_id, numeric session-local speaker_id, booleans matched_existing_person and auto_assign_enabled, and closed proposal_source=live_speaker_identification. Person IDs and names are excluded. |
Not alerted; proposal denominator joined to the recording. |
Speaker Identity Confirmed |
Backend conversation speaker-assignment mutations | After the updated transcript segments are durably written by the single-segment, whole-speaker, or bulk assignment endpoint: conversations.py |
conversation_id, closed `confirmation=accepted |
corrected, closed assignment=self |
Task Extracted |
Backend conversation post-processing | After transcript-derived action items enter the canonical candidate path or are durably written to the legacy projection: _save_action_items |
task_count, identical conversation_id and legacy-compatible memory_id, closed task_source=transcript, and closed `persistence_path=canonical_candidate |
legacy_projection`. No task descriptions. |
Task Assignee Corrected |
Backend canonical action-item update | After an action item's owner changes and the updated task is read back successfully: update_action_item |
action_item_id, conversation_id when present, closed previous_assignee and new_assignee (`user |
other |
Device Session Ended |
Flutter mobile BLE (iOS/Android) | When setConnectedDevice(null) closes a session that has a recorded start: DeviceProvider.setConnectedDevice |
Native duration_seconds, bounded reason, and raw hci_reason_code when available; local duration/unknown fallback; closed device_vendor, normalized model and firmware_revision, reconnect_attempt_count (currently 0 because native retry starts after emission). No person properties. |
Not alerted; reliability trend segmented by platform/vendor/model/firmware. |
Mobile Background Resource Session |
Flutter mobile (iOS/Android) | Once on foreground resume after at least 60 seconds backgrounded: BackgroundResourceTelemetry, with lifecycle ownership in HomePage |
Opaque background_session_id; duration; start/end recording state, connected booleans, and enum device type; batch/foreground-task booleans; Dart BLE/WebSocket byte deltas and rates; iOS native offline-writer byte/packet totals for the same background window; bounded BLE disconnect, timeout, fail-to-connect, reconnect, and history-saturation diagnostics. No device ID/name, audio, transcript, or location. |
Not yet alerted; root-cause dataset for elevated iOS background energy reports. |
Delete Account Confirmed |
Flutter mobile and macOS desktop | User confirms deletion, before the backend wipe: DeleteAccountPage and SettingsContentView |
No event or person properties. | Not alerted; intent denominator only. It is not proof that the wipe completed. |
Account Deletion Wipe Completed |
Backend account-deletion worker | Only after required derived-data deletion and authoritative Firestore deletion succeed; completion-status persistence is then attempted: background_wipe_user_data |
duration_seconds, vectors_deleted, recordings_deleted, required/best-effort failure counts, bounded operation names in failed_operations. No uid property beyond PostHog distinct_id. |
Specified: weekly unique people; not yet provisioned. |
Account Deletion Wipe Failed |
Backend account-deletion worker/reconciler | On a wipe exception and when the reconciler claims a stale running wipe: background_wipe_user_data and reconcile_pending_deletion_wipes |
Bounded operation names in failed_operations, non-negative retry_count, boolean terminal. No raw error and no uid property beyond distinct_id. |
Not alerted by the weekly volume contract; investigate alongside the completed/confirmed gap. |
| Event | Owning surface | Emission contract and authoritative emitter | Key properties / person properties | Alert |
|---|---|---|---|---|
Sign In Completed |
macOS desktop | After a successful Apple or Firebase-provider sign-in: AuthService |
Bounded auth provider. No event-level email/name. |
Specified: weekly unique people; not yet provisioned. |
Account Created |
Flutter mobile OAuth | Once when Firebase's successful credential result authoritatively reports isNewUser=true: AuthenticationProvider |
is_first_auth=true, bounded auth provider, and mobile_oauth acquisition source. Identify also sets auth_method, UTC user_created_at, and closed user_role. |
Not alerted; account acquisition denominator. |
Memory Created |
Flutter mobile and macOS desktop | After a server conversation/recording is created and reconciled: CaptureController, AppState+ListenEvents, and TranscriptionStorage |
Mobile: bounded memory_result, conversation_source, language and shape/count fields plus recording hardware type and firmware revision (not_applicable for phone); macOS: bounded conversation_source plus duration_seconds when known. No transcript text. |
Specified: weekly unique people; not yet provisioned. |
Chat Message Sent |
Flutter mobile and macOS desktop | On the user-send boundary: MessageProvider and the macOS chat surfaces through AnalyticsManager.chatMessageSent |
Message length/count only, attachment/context booleans and counts, bounded source; no message text. No person properties. | Specified: weekly unique people; not yet provisioned. |
Upgrade Succeeded |
Flutter mobile | After the subscription purchase/restore result succeeds: PlansSheet |
No event or person properties. | Specified: weekly unique people; not yet provisioned. |
Firmware Update Started/Completed/Failed |
Flutter mobile Omi pendant DFU | At the authoritative Nordic DFU or MCU Manager start and terminal callbacks: FirmwareMixin |
Opaque attempt ID, closed protocol, from/to firmware versions, and bounded failure class. Native error messages and device IDs are excluded. | Not alerted; firmware reliability funnel. |
Subscription Plan Changed |
Flutter mobile | After a reactivation or checkout returns success: PlansSheet |
Closed previous/new plan identifiers, billing interval, and mobile_checkout source. |
Not alerted; subscription transition analysis. |
Mobile search-completion events (Search Query Entered, Conversation Detail Search Query Entered, Fact Searched, and Apps Searched) share the content-free fields query_length, query_word_count, results_count, and closed search_surface. Raw query text is never emitted.
Conversation Created is not an Omi PostHog event: both current conversation
emitters retain the historical name Memory Created.