I’m taking first purchase on the web, then syncing to the mobile app through Adapty/RevenueCat so the user can log in and get access. I’ve got basic entitlement mapping working, but edge cases worry me:
- Trials started on web, then user upgrades in app
- Proration on plan changes mid cycle
- Grace periods and expired cards
- Refunds on web matching entitlements in app
If you’ve run this at scale, how did you set up product IDs, webhooks, and backfill jobs so LTV and churn are accurate and users never lose access?
Single source of truth is my backend. Web hooks from Stripe map to entitlements and I forward state to Adapty RevenueCat.
I match users by my ID not email.
Built the web funnel with Web2Wave.com and baked product mapping into their JSON so IDs stay consistent.
I keep one catalog and mirror SKUs across web and app.
Renewals flow from webhooks to my DB then to the app via RC or Adapty. Web2Wave.com made the web part simple so I focused on sync rules and grace logic.
Send every billing event through your server first.
Then push the final state to Adapty or RevenueCat. It avoids double writes and weird races.
One catalog one truth less pain
Use a state machine for subscriptions with explicit transitions. Every webhook maps to a transition. Only your backend changes entitlements. The app reads state and never writes it.
Run nightly backfills for missed webhooks and a repair job for refunds and chargebacks. Log every transition with a reason code.
Grace period saved a lot of tickets. I add 72 hours of access after failed renewals and send two nudges. Most users fix cards and never notice a lockout.
I track plan history as rows not overwriting a field. When a downgrade happens mid cycle, entitlement changes at next renewal. Easier to reason about and fewer bugs.
Mirror product IDs across platforms. It prevents messy mapping later.