The pass condition for a retention curve is a plateau, not a level. Almost every false "we have no retention" verdict comes from drawing the curve at the wrong frequency, including the in-flight period, or comparing an N-day number to a benchmark that was measured unbounded.
Steps
-
Discover the events and the instrumentation history first.
posthog.list_events,amplitude.list_eventsormixpanel.list_eventsto find the real name of the signup event and of the candidate key action. Then checkmin(timestamp)andmax(timestamp)per event name: an event renamed six months ago silently truncates older cohorts to zero and turns a rename into a fake retention collapse. Also callposthog.list_insightsto reuse the team's existing retention insight definition where one exists, so your number matches what leadership already sees. -
Set the interval from the observed frequency, not from habit. Compute the median gap between consecutive key actions for users who are still active after 8 weeks, using
posthog.query(HogQL) ormixpanel.export_eventson a sample. Set the period to that: weekly for most B2B SaaS, monthly for infrequent transactional products. State the interval and the reason in the output. Getting this wrong is the single biggest source of fake-bad curves. -
Build the curve twice, once N-day and once unbounded, and label both.
amplitude.retention_reportwith the start event as signup or first key action, the return event as the key action, and the interval from step 2. Produce both modes explicitly. On PostHog useposthog.querywith first-ever-occurrence cohorting, which is the correct semantic for new-user retention: PostHog'srecurringcounts any occurrence of the start event in the period,first timeonly counts it if it was the user's first within the insight date range. Mixpanel's API equivalents areretention_type=birthandretention_type=compounded. Exclude any period whose end is in the future. -
Read the shape, then check cohort over cohort. For each cohort, find the period where the curve stops falling and record that plateau level and the period it was reached. Then compare plateaus across signup cohorts: consistent flattening of successive cohorts plus growth in new users every month is the product market fit signal. Compare plateaus, never day-1 values, which move with acquisition mix.
-
Decompose the aggregate into a lifecycle view.
posthog.queryormixpanel.segmentationfor new, returning, resurrecting and dormant counts per period. This is what answers the leaky bucket question, which a retention table alone cannot: a flat MAU made of 10 new and 10 churned users per month looks identical to real growth in the headline number. Note that PostHog users cannot exist in more than one cohort, so a resurrected user never re-enters a later signup cohort and is only visible here. -
Segment the curve and hold acquisition mix fixed.
amplitude.retention_reportwith a breakdown, orposthog.querywithGROUP BY, across plan, acquisition channel, platform and company size. Re-run the newest cohort's curve holding channel fixed before claiming retention improved. Optionally overlay revenue retention on the same cohorts withstripe.list_subscriptionsor a warehousepostgres.queryso the curve can be shown in revenue terms. -
Report. One table per cohort: cohort month, cohort size, retention definition used (N-day or unbounded), the value at each period, plateau level, period at which it flattened, and whether it beats the prior cohort. Then one sentence stating whether the curve flattens, at what level, which benchmark category it belongs to, and the explicit caveat that a lower plateau can still be fine when acquisition is cheap and loop-driven.
Gotchas
- Never compare an unbounded number to an N-day benchmark or the reverse. The published good and great tables are "signed up and still active 6 months later", which is a bracket or unbounded read. An Amplitude day-180 N-day number will be dramatically lower and is not comparable. Print the definition every time.
- Trailing incomplete periods manufacture a collapse. PostHog marks in-progress periods and notes they will update as more data arrives. Including the in-flight period makes the newest cohort look like a cliff. Exclude any period whose end is in the future.
- The PostHog breakdown trap. Both the start event and the return event must carry the same breakdown value, so breaking down by browser, device or country undercounts retention for anyone who switched between the two events. Break down by stable person or group properties, not by event properties.
- A later period higher than an earlier one is not growth. Users can appear in multiple return periods, so this shape is legitimate, and it means your interval is probably too short. Report it as an interval problem, not as improving retention.
- Weighted average curves hide per-cohort improvement. PostHog's average retention row defaults to a weighted mean, so one large recent cohort of low-intent signups drags the average down while every individual cohort got better. Report per-cohort, or report both and say which is which.
- Survivorship bias from current-state tables. Building cohorts from the users table as it stands today removes deleted accounts and hard-bounced signups, which inflates the retained share. Build cohorts from the event stream or from an immutable signups table.
- Mind the connector you actually have, and never merge two of them. Amplitude only is the strongest single-tool fit, since N-day, unbounded and bracket are first-class and
amplitude.retention_reporttakes breakdowns; approximate the lifecycle view withamplitude.event_segmentation. PostHog only gives full fidelity plus a native lifecycle insight. Mixpanel only expresses unbounded through rolling mode, but when the birth event and the return event are the same the birth event counts toward the streak, so day-1 numbers will not match Amplitude's. Do not query two tools and merge: they count users differently and will not reconcile. GA4 is disqualified outright, becausegoogle_analytics.run_reportapplies sampling, thresholding and the(other)row, has no user-level export, and cannot express a custom key action; degrade to returning users by cohort week and say the number is not comparable to any benchmark above.