Developers

Analytics

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.tsx only when:
    • VITE_POSTHOG_API_KEY is set
    • build is not dev (!import.meta.env.DEV)
  • Enabled options:
    • autocapture: true
    • capture_pageview: true

Desktop (apps/desktop + plugins/analytics)

  • Frontend calls analyticsCommands.event, analyticsCommands.setProperties, and analyticsCommands.identify.
  • Rust plugin forwards to hypr_analytics::AnalyticsClient in plugins/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-fingerprint and auth user ID into request extensions in apps/api/src/auth.rs.
  • LLM/STT/trial analytics emit from backend crates (details below).

Identity and distinct IDs

SurfaceDistinct IDIdentify behavior
Desktop custom eventsMachine fingerprint (hypr_host::fingerprint())identify(userId, payload) sends PostHog $identify with $anon_distinct_id = machine fingerprint.
Web custom/autocapture eventsPostHog browser distinct IDAuth callback calls posthog.identify(userId, { email }).
API $ai_generationx-device-fingerprint if present, else generation_idOptional user_id also included as event property.
API $stt_requestx-device-fingerprint if present, else random UUIDOptional user_id also included as event property.
API trial eventsAuthenticated user_idNo separate $identify call here.

Automatic desktop event enrichment

Every desktop event(...) call is enriched in plugins/analytics/src/ext.rs with:

PropertyValue
app_versionenv!("APP_VERSION")
app_identifierTauri app identifier
git_hashtauri_plugin_misc::get_git_hash()
bundle_idTauri app identifier
$set.app_versionuser 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

EventPropertiesSource
hero_section_viewedtimestampapps/web/src/routes/_view/index.tsx
download_clickedHomepage: platform, timestampapps/web/src/components/download-button.tsx
download_clickedDownload page: platform, spec, source ("download_page")apps/web/src/routes/_view/download/index.tsx
reminder_requestedplatform, timestamp, emailapps/web/src/routes/_view/index.tsx
os_waitlist_joinedplatform, timestamp, emailapps/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 }) in apps/web/src/routes/_view/callback/auth.tsx.

Desktop product events

EventPropertiesSource
show_main_windownone (plus auto-enriched desktop props)plugins/windows/src/ext.rs
onboarding_step_viewedstep, platformapps/desktop/src/onboarding/index.tsx
onboarding_completednoneapps/desktop/src/onboarding/final.tsx
user_signed_innoneapps/desktop/src/auth/context.tsx
trial_flow_client_errorproperties.error (nested object)apps/desktop/src/onboarding/account/trial.tsx
trial_flow_skippedproperties.reason (already_pro or already_trialing)apps/desktop/src/onboarding/account/trial.tsx
data_importedsourceapps/desktop/src/settings/data/index.tsx
note_createdhas_event_idapps/desktop/src/store/tinybase/store/sessions.ts, apps/desktop/src/shared/main/useNewNote.ts
file_uploadedAudio: file_type = "audio"; Transcript: file_type = "transcript", token_countapps/desktop/src/session/components/floating/options-menu.tsx
session_startedhas_calendar_event, stt_provider, stt_modelapps/desktop/src/stt/useStartListening.ts
tab_openedviewapps/desktop/src/store/zustand/tabs/basic.ts
search_performednoneapps/desktop/src/search/contexts/ui.tsx
note_editedhas_content (currently emitted as true)apps/desktop/src/session/components/note-input/raw.tsx
note_enhancedVariant A: is_auto; Variant B: is_auto, llm_provider, llm_model, template_idapps/desktop/src/session/components/note-input/header.tsx, apps/desktop/src/services/enhancer/index.ts
message_sentnoneapps/desktop/src/chat/components/input/hooks.ts
session_exportedModal export: format, include_summary, include_transcriptapps/desktop/src/session/components/outer-header/overflow/export-modal.tsx
session_exportedPDF export: format = "pdf", view_type, has_transcript, has_enhanced, has_memoapps/desktop/src/session/components/outer-header/overflow/export-pdf.tsx
session_exportedTranscript export: format = "vtt", word_countapps/desktop/src/session/components/outer-header/overflow/export-transcript.tsx
session_deletedincludes_recording (currently always true)apps/desktop/src/session/components/outer-header/overflow/delete.tsx
settings_changedautostart, notification_detect, save_recordings, telemetry_consentapps/desktop/src/settings/general/index.tsx
ai_provider_configuredproviderapps/desktop/src/settings/ai/shared/index.tsx
upgrade_clickedplan ("pro")apps/desktop/src/settings/general/account.tsx
user_signed_outnoneapps/desktop/src/settings/general/account.tsx

Desktop notification events

EventPropertiesSource
collapsed_confirmnoneplugins/notification/src/handler.rs
expanded_acceptnoneplugins/notification/src/handler.rs
dismissnoneplugins/notification/src/handler.rs
collapsed_timeoutnoneplugins/notification/src/handler.rs
option_selectednoneplugins/notification/src/handler.rs

API/server events

EventPropertiesSource
$stt_request$stt_provider, $stt_duration, optional user_idcrates/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_idcrates/llm-proxy/src/analytics.rs
trial_startedplan, source (desktop or web)crates/api-subscription/src/trial.rs, crates/api-subscription/src/routes/billing.rs
trial_skippedreason = "not_eligible", sourcecrates/api-subscription/src/trial.rs, crates/api-subscription/src/routes/billing.rs
trial_failedreason (stripe_error, customer_error, rpc_error), sourcecrates/api-subscription/src/trial.rs, crates/api-subscription/src/routes/billing.rs

User property catalog

PostHog user properties are set via $set, $set_once, and $identify payloads.

PropertyHow it is setSource
emailidentify(..., { email }) (desktop and web)apps/desktop/src/auth/context.tsx, apps/web/src/routes/_view/callback/auth.tsx
account_created_dateidentify(..., { set: { ... } })apps/desktop/src/auth/context.tsx
is_signed_uptrue on sign-in identify, false on sign-out setPropertiesapps/desktop/src/auth/context.tsx, apps/desktop/src/settings/general/account.tsx
platformidentify(..., set.platform)apps/desktop/src/auth/context.tsx
os_versionidentify(..., set.os_version)apps/desktop/src/auth/context.tsx
app_versionidentify(..., set.app_version) and per-event $set.app_version enrichmentapps/desktop/src/auth/context.tsx, plugins/analytics/src/ext.rs
telemetry_opt_outsetProperties({ set: { telemetry_opt_out } })apps/desktop/src/settings/general/index.tsx
has_configured_aisetProperties({ set: { has_configured_ai: true } })apps/desktop/src/settings/ai/shared/index.tsx
spoken_languagessettings persister setPropertiesapps/desktop/src/store/tinybase/persister/settings/persister.ts
current_stt_providersettings persister setPropertiesapps/desktop/src/store/tinybase/persister/settings/persister.ts
current_stt_modelsettings persister setPropertiesapps/desktop/src/store/tinybase/persister/settings/persister.ts
current_llm_providersettings persister setPropertiesapps/desktop/src/store/tinybase/persister/settings/persister.ts
current_llm_modelsettings persister setPropertiesapps/desktop/src/store/tinybase/persister/settings/persister.ts
planserver set_properties on successful trial startcrates/api-subscription/src/trial.rs
trial_end_dateserver 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_consent config side effect calls analyticsCommands.setDisabled(!value).
    • When disabled, desktop plugin drops event, setProperties, and identify calls.
    • Source: apps/desktop/src/shared/config/registry.ts, plugins/analytics/src/ext.rs.
  • Desktop PostHog initialization:
    • Release builds require POSTHOG_API_KEY at 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.
  • 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.
  • Note on scope:
    • Desktop telemetry_consent only controls the desktop plugin path. No code path currently applies that toggle to server-side $stt_request / $ai_generation / trial events.

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

  1. Search for all emitters:
    • analyticsCommands.event(
    • analyticsCommands.setProperties(
    • analyticsCommands.identify(
    • AnalyticsPayload::builder("...)`
    • posthog.capture( / posthog.identify(
  2. Verify payload keys at each callsite (watch for nested objects like properties: {...}).
  3. Re-run this inventory after any analytics refactor in plugins/analytics, crates/analytics, crates/llm-proxy, crates/transcribe-proxy, or crates/api-subscription.