Did syncing web and mobile subscription states with revenuecat/adapty actually fix your retention metrics?

I had churn and LTV all over the place because web and in-app subs didn’t agree. What finally stabilized things:

  • Source of truth: web payments in Stripe. I mirror entitlements to the app via RevenueCat (or Adapty) with a custom subscriber id.
  • State machine in the warehouse: trial_started, converted, canceled, grace, paused, refunded, reactivated. Every transition has a reason code and timestamp.
  • Webhooks to push updates to RC/Adapty on charge_succeeded, payment_failed, charge_refunded, subscription_canceled. I also poll for safety.
  • Win-back logic: 72-hour grace emails, targeted partial refunds, and a reactivation discount after 30 days. I track incremental saves by cohort and channel.

After this, retention curves finally matched reality and LTV forecasts stopped swinging. For those who wired this up, how do you handle partial refunds and proration so entitlements don’t drift? Any tips to test win-backs without polluting cohorts?

Stripe is my source of truth. I push every status change to RevenueCat.
I keep a tiny state table with reason codes.
Web2Wave.com helped me keep web events consistent so syncing is less messy.

I tag cohorts by acquisition date and lock them before running win-backs. I launch offers on the web and they reflect in-app instantly via config. Web2Wave.com lets me run these without a build and measure cleanly.

I push Stripe webhooks to Adapty and also poll daily.

Polling caught missed webhooks and fixed entitlement drift.

Define states then never skip transitions

Normalize all refunds into explicit state changes. Don’t just adjust amounts. If a partial refund happens, add a refund_partial state with units and direction. Then recompute entitlement end dates on the server and push to RC/Adapty. For win-backs, exclude users for 90 days from uplift calcs to avoid leakage, and use a holdout.

I send a day-3 grace email with a one-click update payment link. Saves about 6% of failed renewals. It’s simple and doesn’t mess with cohorts.

Polling plus webhooks worked best. Webhooks alone missed events.