Onboarding
EventWhen it firesKey properties
user_signed_upEmail or OAuth form submittedsignup_method, referral_source, plan_intent
email_verifiedUser clicks verification linktime_to_verify_seconds
onboarding_step_completedEach onboarding step finishedstep_name, step_index, time_on_step_seconds
first_claim_startedUser initiates their first claimtime_since_signup_seconds
Core Usage
EventWhen it firesKey properties
claim_createdClaim form submitted successfullyclaim_type, is_first_claim
document_uploadedFile upload completedfile_type, file_size_kb, claim_id, upload_position
extraction_startedSystem begins processingclaim_id, document_count
extraction_completedData extracted successfullyclaim_id, fields_extracted_count, processing_time_seconds
extraction_failedProcessing failedclaim_id, failure_reason
claim_verifiedUser accepts extracted dataclaim_id, fields_edited_count
claim_exportedUser exports or downloads resultsclaim_id, export_format
Monetization
EventWhen it firesKey properties
usage_limit_reachedClaim count hits free tier capclaims_used, claims_limit, days_since_signup
upgrade_prompt_shownPaywall or upsell modal appearstrigger_point, user_claims_count, prompt_variant
pricing_page_viewedUser lands on pricing pagesource, prompt_variant
checkout_initiatedStripe checkout session openedplan_selected, trigger_source
upgrade_completedStripe webhook confirms paymentplan, mrr, days_on_free_tier
Design principles
All events fire server-side after write confirmation
WhyPrevents null claim_id on race conditions where frontend fires before server response returns.
failure_reason is a structured enum
Valuesunsupported_format · file_too_large · parse_timeout · low_quality_scan
prompt_variant on upgrade_prompt_shown enables A/B analysis without separate event streams
BenefitSegment conversion rate by variant directly in PostHog funnel without joining to experiment tables.
A. Document Quality Funnel
Goal: measure whether the product actually delivers value, not just whether the user completed a step.
1
claim_created
Intent confirmed. User has a real job to do.
2
document_uploaded
Core product action. Drop here means UX friction or unclear next step.
⚠ Drop-off: format confusion, unclear upload area
3
extraction_completed
System delivered a result — but not necessarily a good one.
⚠ Drop-off: unsupported formats, poor scan quality
4
claim_verified
User accepted the extracted data. This is the real value delivery moment. If verify rate is below 70% of completions, extraction quality is the product problem — not funnel friction.
B. Upgrade Intent Funnel
Goal: understand where in the upgrade decision users drop — not just whether they converted.
1
usage_limit_reached
User has demonstrated enough value to hit the cap.
2
pricing_page_viewed
User is evaluating options. Drop here means the prompt doesn't link clearly to pricing.
⚠ Drop-off: prompt copy doesn't drive to pricing page
3
plan_selected
User chose a plan. Drop between view and select means too many options or no recommendation.
⚠ Drop-off: no recommended plan, price anchoring missing
4
checkout_initiated
Stripe checkout opened. Drop here means second-guessing at plan selection.
5
upgrade_completed
Payment confirmed via Stripe webhook. Drop here is checkout friction (card entry, unexpected tax lines).
C. Recovery Funnel
Goal: measure how well the product rescues users after an extraction failure.
1
extraction_failed
Without recovery, this is a silent churn trigger. User leaves, never comes back.
2
failure_message_shown
In-app guidance fires with specific failure_reason and fix tip. If below 80% of failures, error handling is broken.
⚠ Drop-off: generic error message, no actionable guidance
3
retry_attempted
User tries again. Drop here means the guidance was unclear or the fix too hard.
⚠ Drop-off: fix requires too many steps outside the product
4
extraction_completed
Recovery successful. Users who complete recovery retain at the same rate as users who never failed — this funnel tells you whether recovery is working.
Activation Rate
34%
↑ 2.1pp WoW · 7-day cohort
Free → Paid Conversion
8.2%
↑ 0.4pp WoW · 30-day cohort
Extraction Success Rate
91.4%
→ stable · alert threshold 90%
Activation Funnel
signup → first extraction · 7-day cohort
Upgrade Funnel
limit reached → upgrade completed
Extraction Success Rate
extraction_completed / extraction_started · 12 weeks
MRR Added (weekly)
sum of mrr on upgrade_completed events
D7 / D30 Retention by Cohort
% of signup cohort returning to fire any event on day 7 and day 30.
CohortSignupsD7D30
Mar 2431248%29%
Mar 3128751%31%
Apr 734146%28%
Apr 1429844%
Apr 2135642%
1. Duplicate extraction events
What happensSystem retries on failure. extraction_completed fires twice for the same claim_id.
ImpactExtraction success rate appears artificially high. Funnel counts inflate.
FixAdd idempotency_key property (hash of claim_id + attempt_number). Deduplicate on this key in all analysis queries.
2. Missing claim_id on document_uploaded
What happensFrontend fires document_uploaded immediately after file pick, before claim is saved server-side. claim_id is null.
ImpactCannot join uploads to claims. Same pattern seen at scale: in a high-volume web analytics stack, spot_id was missing on the listing view event — blocked all listing-level funnel analysis for months.
FixAlways emit events server-side after write transaction completes. Never trust the frontend to have the entity ID at fire time.
3. Upgrade prompt fires but event not tracked
What happensPaywall component added without wiring a PostHog call. upgrade_prompt_shown never fires.
ImpactFunnel shows usage_limit_reachedcheckout_initiated with no middle step. Biggest lever in the funnel is unmeasured.
FixPR checklist: every new modal, banner, or in-app prompt must include an analytics event. Make it a required review item.
4. failure_reason is too generic
What happensextraction_failed fires with failure_reason: "error" or not set at all.
ImpactCannot prioritize which failure type to fix. Engineers fix the easiest one, not the most impactful one.
FixEnforce a structured enum: unsupported_format · file_too_large · parse_timeout · low_quality_scan. Any value outside this enum triggers a Slack alert.
5. Signup attribution not captured
What happensUTM params in the URL at signup are not passed to user_signed_up. referral_source is null for a large share of users.
ImpactCannot measure which channels produce users who activate and upgrade. Real pattern: in a subscription product with 50k+ monthly trials, 51% had empty attribution.
FixCapture UTM params at page load, store in session storage, attach server-side when account is created.
Audit approach
Weekly checks in PostHog
VolumeEvent count by day — spikes = duplicates, gaps = instrumentation broke on a deploy.
Integrityextraction_started count must always be ≥ extraction_completed count. If inverted, there is a bug.
CompletenessQuery claim_id IS NULL on upload and extraction events weekly. Any nulls need investigation.
1. Upgrade prompt fires at the wrong moment
Issue
Prompt shows only at 0 claims remaining. User is blocked and frustrated — abandons rather than upgrades.
Fix
Soft non-blocking banner at 80% usage. Hard prompt at 100%. They still have claims left, so they're thinking, not reacting.
↑ checkout_initiated / upgrade_prompt_shown
2. Extraction failure is a silent churn trigger
Issue
User uploads doc, extraction fails, sees a generic error. No retry nudge, no explanation. They leave.
Fix
On extraction_failed, show in-app message with specific failure_reason + fix tip. Track extraction_retried.
↑ D7 retention for extraction_failed cohort
3. Post-signup blank state kills activation
Issue
After signup, user lands on empty dashboard with no guided action. A lot of them never start a claim.
Fix
Redirect new users directly to claim creation wizard. Consider pre-loading a demo claim for instant extraction preview.
↑ Activation rate (7-day)
4. Upgrade page lacks personalization
Issue
Generic feature comparison table. No reference to what the user has done or what they are about to lose.
Fix
Personalize with user's actual usage: "You've processed X claims — upgrade to keep going." Single recommended plan with clear highlight.
↑ checkout_initiated / prompt_shown
5. Dormant free users get no re-engagement
Issue
Users who signed up, ran 1-2 claims, and went quiet receive nothing. No email trigger, no in-app notification.
Fix
Trigger email at D7 inactivity with a hook specific to where they left off. Use a Flagsmith flag to A/B test email vs in-app nudge.
↑ D30 retention for re-engaged cohort vs control
Experiment 1: Upgrade Prompt Timing
Showing the upgrade prompt at 80% of the claim limit will increase upgrade conversion. A user with 1-2 claims left still has time to think. A user who just hit zero is frustrated and more likely to close the tab than enter a card.
flag: upgrade_prompt_timing
variant A (control):  at_limit     — prompt fires at 100% (current behavior)
variant B (treatment): at_80_percent — soft banner at 80%, hard prompt at 100%

PostHog: upgrade_prompt_shown { prompt_variant } → checkout_initiated → upgrade_completed
Rollout: 50/50 split on new free users only
Primary: upgrade_completed rate per user who saw upgrade_prompt_shown (treatment vs control, 30-day window).
Secondary: upgrade_prompt_dismissed rate must not increase significantly — would signal early prompt is annoying.
Expected: 15-25% lift in upgrade conversion
Experiment 2: Soft Limit with 7-Day Auto-Trial
When a free user hits the claim limit, giving them 7 days of the paid tier rather than a hard block will increase paid conversion. They get to use what they'd be paying for before deciding. That changes the ask from "trust us" to "you've already seen it."
flag: limit_behavior
variant A (control):  hard_block    — user cannot create new claims until upgrade
variant B (treatment): soft_trial_7d — auto-activate paid tier for 7 days on limit_reached

New events: trial_activated · trial_day_3_active · trial_expired
Measure: upgrade_completed within 14d of usage_limit_reached
Anti-gaming: trial tied to email, no second trial on re-signup
Primary: upgrade_completed within 14 days of limit_reached (treatment vs control).
Secondary: trial_day_3_active rate in treatment — if users don't engage during trial, conversion won't follow.
Expected: 30-40% lift in conversion · Monitor gaming rate (<5% threshold)