Scope
This is a code-derived inventory of what Char sends to PostHog across:
apps/web(browser events)apps/desktop+plugins/analytics(desktop events)apps/api+ proxy/subscription crates (server-side events)
Collection paths
Web (apps/web)
- PostHog is initialized in
apps/web/src/providers/posthog.tsxonly when:VITE_POSTHOG_API_KEYis set- build is not dev (
!import.meta.env.DEV)
- Enabled options:
autocapture: truecapture_pageview: true
Desktop (apps/desktop + plugins/analytics)
- Frontend calls
analyticsCommands.event,analyticsCommands.setProperties, andanalyticsCommands.identify. - Rust plugin forwards to
hypr_analytics::AnalyticsClientinplugins/analytics/src/ext.rs. - Distinct ID for desktop telemetry is
hypr_host::fingerprint()(hashed machine UID).
API/server (apps/api + crates)
- API builds a PostHog client in production in
apps/api/src/main.rs. - Request middleware maps
x-device-fingerprintand auth user ID into request extensions inapps/api/src/auth.rs. - LLM/STT/trial analytics emit from backend crates (details below).
Identity and distinct IDs
| Surface | Distinct ID | Identify behavior |
|---|---|---|
| Desktop custom events | Machine fingerprint (hypr_host::fingerprint()) | identify(userId, payload) sends PostHog $identify with $anon_distinct_id = machine fingerprint. |
| Web custom/autocapture events | PostHog browser distinct ID | Auth callback calls posthog.identify(userId, { email }). |
API $ai_generation | x-device-fingerprint if present, else generation_id | Optional user_id also included as event property. |
API $stt_request | x-device-fingerprint if present, else random UUID | Optional user_id also included as event property. |
| API trial events | x-device-fingerprint if present (desktop), else authenticated user_id | user_id is included as an event property when distinct ID is fingerprint. No separate $identify call here. |
Automatic desktop event enrichment
Every desktop event(...) call is enriched in plugins/analytics/src/ext.rs with:
| Property | Value |
|---|---|
app_version | env!("APP_VERSION") |
app_identifier | Tauri app identifier |
git_hash | tauri_plugin_misc::get_git_hash() |
bundle_id | Tauri app identifier |
$set.app_version | user property update on each event |
This enrichment applies to desktop frontend events and Rust plugin event_fire_and_forget events (for example notification/window events).
Event catalog
Web custom events
| Event | Properties | Source |
|---|---|---|
hero_section_viewed | timestamp | apps/web/src/routes/_view/index.tsx |
download_clicked | Homepage: platform, timestamp | apps/web/src/components/download-button.tsx |
download_clicked | Download page: platform, spec, source ("download_page") | apps/web/src/routes/_view/download/index.tsx |
reminder_requested | platform, timestamp, email | apps/web/src/routes/_view/index.tsx |
os_waitlist_joined | platform, timestamp, email | apps/web/src/routes/_view/index.tsx |
Notes:
- PostHog autocapture and pageview are also on (production only), so PostHog default browser events are collected in addition to the custom events above.
- Web auth callback calls
identify(userId, { email })inapps/web/src/routes/_view/callback/auth.tsx.
Desktop product events
| Event | Properties | Source |
|---|---|---|
show_main_window | none (plus auto-enriched desktop props) | plugins/windows/src/ext.rs |
onboarding_step_viewed | step, platform | apps/desktop/src/onboarding/index.tsx |
onboarding_completed | none | apps/desktop/src/onboarding/final.tsx |
user_signed_in | none | apps/desktop/src/auth/context.tsx |
trial_flow_client_error | properties.error (nested object) | apps/desktop/src/onboarding/account/trial.tsx |
trial_flow_skipped | properties.reason (already_pro or already_trialing) | apps/desktop/src/onboarding/account/trial.tsx |
data_imported | source | apps/desktop/src/settings/data/index.tsx |
note_created | has_event_id | apps/desktop/src/store/tinybase/store/sessions.ts, apps/desktop/src/shared/main/useNewNote.ts |
file_uploaded | Audio: file_type = "audio"; Transcript: file_type = "transcript", token_count | apps/desktop/src/session/components/floating/options-menu.tsx |
session_started | has_calendar_event, stt_provider, stt_model | apps/desktop/src/stt/useStartListening.ts |
tab_opened | view | apps/desktop/src/store/zustand/tabs/basic.ts |
search_performed | none | apps/desktop/src/search/contexts/ui.tsx |
note_edited | has_content (currently emitted as true) | apps/desktop/src/session/components/note-input/raw.tsx |
note_enhanced | Variant A: is_auto; Variant B: is_auto, llm_provider, llm_model, template_id | apps/desktop/src/session/components/note-input/header.tsx, apps/desktop/src/services/enhancer/index.ts |
message_sent | none | apps/desktop/src/chat/components/input/hooks.ts |
session_exported | Modal export: format, include_summary, include_transcript | apps/desktop/src/session/components/outer-header/overflow/export-modal.tsx |
session_exported | PDF export: format = "pdf", view_type, has_transcript, has_enhanced, has_memo | apps/desktop/src/session/components/outer-header/overflow/export-pdf.tsx |
session_exported | Transcript export: format = "vtt", word_count | apps/desktop/src/session/components/outer-header/overflow/export-transcript.tsx |
session_deleted | includes_recording (currently always true) | apps/desktop/src/session/components/outer-header/overflow/delete.tsx |
settings_changed | autostart, notification_detect, save_recordings, telemetry_consent | apps/desktop/src/settings/general/index.tsx |
ai_provider_configured | provider | apps/desktop/src/settings/ai/shared/index.tsx |
upgrade_clicked | plan ("pro") | apps/desktop/src/settings/general/account.tsx |
user_signed_out | none | apps/desktop/src/settings/general/account.tsx |
Desktop notification events
| Event | Properties | Source |
|---|---|---|
collapsed_confirm | none | plugins/notification/src/handler.rs |
expanded_accept | none | plugins/notification/src/handler.rs |
dismiss | none | plugins/notification/src/handler.rs |
collapsed_timeout | none | plugins/notification/src/handler.rs |
option_selected | none | plugins/notification/src/handler.rs |
API/server events
| Event | Properties | Source |
|---|---|---|
$stt_request | $stt_provider, $stt_duration, optional user_id | crates/transcribe-proxy/src/analytics.rs |
$ai_generation | $ai_provider, $ai_model, $ai_input_tokens, $ai_output_tokens, $ai_latency, $ai_trace_id, $ai_http_status, $ai_base_url, optional $ai_total_cost_usd, optional user_id | crates/llm-proxy/src/analytics.rs |
trial_started | plan, source (desktop or web) | crates/api-subscription/src/trial.rs, crates/api-subscription/src/routes/billing.rs |
trial_skipped | reason = "not_eligible", source | crates/api-subscription/src/trial.rs, crates/api-subscription/src/routes/billing.rs |
trial_failed | reason (stripe_error, customer_error, rpc_error), source | crates/api-subscription/src/trial.rs, crates/api-subscription/src/routes/billing.rs |
User journey tracking
The current product telemetry for the common activation journey is:
| Journey step | Primary event(s) | Current coverage |
|---|---|---|
| 1. Visitor downloads from website | download_clicked | Tracked on homepage and download page variants. |
| 2. Installs app from DMG | none | No explicit install-complete event today. |
| 3. Opens app | show_main_window | Tracked when the main window is shown. |
| 4. Signs up/signs in | user_signed_in + desktop $identify | Tracked from desktop auth state changes. |
| 5. Completes onboarding | onboarding_step_viewed, onboarding_completed | Step-level and completion coverage exists. |
| 6a. Creates a note | note_created | Tracked for both empty and event-backed notes. |
| 6b. Transcribes a meeting | session_started, $stt_request | UI intent (session_started) + server usage ($stt_request). |
| 6c. Generates a summary | note_enhanced, $ai_generation | UI intent (note_enhanced) + server model usage ($ai_generation). |
Notes:
- Step 2 (install) is currently inferred from first app-open behavior (
show_main_window), not directly measured. - For transcription/summarization, server telemetry (
$stt_request,$ai_generation) is the stronger "actual work happened" signal. - Desktop event stitching relies on machine fingerprint distinct IDs plus desktop
identify(...)during auth.
Suggested funnel definitions
Use this sequence for a practical activation funnel:
download_clickedshow_main_windowuser_signed_inonboarding_completednote_created$stt_request(optionally duration threshold, e.g.>= 300s)note_enhanced(or$ai_generation)
User property catalog
PostHog user properties are set via $set, $set_once, and $identify payloads.
| Property | How it is set | Source |
|---|---|---|
email | identify(..., { email }) (desktop and web) | apps/desktop/src/auth/context.tsx, apps/web/src/routes/_view/callback/auth.tsx |
account_created_date | identify(..., { set: { ... } }) | apps/desktop/src/auth/context.tsx |
is_signed_up | true on sign-in identify, false on sign-out setProperties | apps/desktop/src/auth/context.tsx, apps/desktop/src/settings/general/account.tsx |
platform | identify(..., set.platform) | apps/desktop/src/auth/context.tsx |
os_version | identify(..., set.os_version) | apps/desktop/src/auth/context.tsx |
app_version | identify(..., set.app_version) and per-event $set.app_version enrichment | apps/desktop/src/auth/context.tsx, plugins/analytics/src/ext.rs |
telemetry_opt_out | setProperties({ set: { telemetry_opt_out } }) | apps/desktop/src/settings/general/index.tsx |
has_configured_ai | setProperties({ set: { has_configured_ai: true } }) | apps/desktop/src/settings/ai/shared/index.tsx |
spoken_languages | settings persister setProperties | apps/desktop/src/store/tinybase/persister/settings/persister.ts |
current_stt_provider | settings persister setProperties | apps/desktop/src/store/tinybase/persister/settings/persister.ts |
current_stt_model | settings persister setProperties | apps/desktop/src/store/tinybase/persister/settings/persister.ts |
current_llm_provider | settings persister setProperties | apps/desktop/src/store/tinybase/persister/settings/persister.ts |
current_llm_model | settings persister setProperties | apps/desktop/src/store/tinybase/persister/settings/persister.ts |
plan | server set_properties on successful trial start | crates/api-subscription/src/trial.rs |
trial_end_date | server set_properties on successful trial start (UTC now + 14 days) | crates/api-subscription/src/trial.rs |
Telemetry controls and environment behavior
- Desktop opt-out:
telemetry_consentconfig side effect callsanalyticsCommands.setDisabled(!value).- When disabled, desktop plugin drops
event,setProperties, andidentifycalls. - Source:
apps/desktop/src/shared/config/registry.ts,plugins/analytics/src/ext.rs.
- Desktop PostHog initialization:
- Release builds require
POSTHOG_API_KEYat compile time. - Debug builds use
option_env!("POSTHOG_API_KEY"); if missing, events are not sent to PostHog (they only hit local tracing fallback). - Source:
plugins/analytics/src/lib.rs,crates/analytics/src/lib.rs.
- Release builds require
- Web:
- PostHog is not initialized in dev mode.
- Source:
apps/web/src/providers/posthog.tsx.
- API:
- PostHog client is active only in non-debug builds (production requires
POSTHOG_API_KEY). - Source:
apps/api/src/main.rs.
- PostHog client is active only in non-debug builds (production requires
- Note on scope:
- Desktop
telemetry_consentonly controls the desktop plugin path. No code path currently applies that toggle to server-side$stt_request/$ai_generation/ trial events.
- Desktop
Feature flags
Feature flag checks are wired through PostHog capability in hypr_analytics, but current desktop feature strategy is hardcoded:
Feature::Chat => FlagStrategy::Hardcoded(true)- Source:
plugins/flag/src/feature.rs.
If a feature uses FlagStrategy::Posthog(key), the check resolves via is_feature_enabled(flag_key, distinct_id) with desktop machine fingerprint as distinct ID.
How to update this document
- Search for all emitters:
analyticsCommands.event(analyticsCommands.setProperties(analyticsCommands.identify(AnalyticsPayload::builder("...)`posthog.capture(/posthog.identify(
- Verify payload keys at each callsite (watch for nested objects like
properties: {...}). - Re-run this inventory after any analytics refactor in
plugins/analytics,crates/analytics,crates/llm-proxy,crates/transcribe-proxy, orcrates/api-subscription.