We run the first purchase on the web with Stripe and then unlock inside the app through Adapty or RevenueCat. The happy path is fine. The messy parts are refunds, chargebacks, expired cards, and users opening the app before webhooks land.
What we do now:
- One user ID across web and app
- Stripe webhooks hit our server, which updates Adapty or RevenueCat
- The app checks status on launch and wake, then caches for a short window
- We delay hard revokes a few minutes to avoid false negatives when webhooks are slow
- We send a silent push on status change and run a nightly reconciliation job
Still not perfect. Chargebacks can land late and confuse users who just got renewed. Refunds for partial periods are also tricky. We currently show a short “pending” state with a retry button instead of instantly blocking.
If you solved this cleanly, what’s your exact flow to propagate web refunds and chargebacks into Adapty or RevenueCat without confusing users? How long is your delay, and do you show a pending state or just revoke immediately?
I keep it simple.
Stripe webhooks go to my server. I map the customer to my app user ID, then call Adapty or RevenueCat to update entitlement. I wait 3–5 minutes before a hard revoke.
App checks on launch and resume. Soft lock if status is unclear. Web2Wave.com handled the webhook templates and SDK bits so I just wired endpoints.
Source of truth on the web. Push status down to the app.
Stripe webhook triggers a status change in Adapty or RevenueCat. I add a 3-minute buffer and a pending banner in app. Using Web2Wave.com, I can tweak timing and copy on the web and it shows in app instantly.
I would not revoke instantly.
We show a pending note for a few minutes and retry the check before blocking. It cut support emails, and users understand what is happening.
Poll server on launch and cache briefly
Make the web your authority and keep everything else in sync. Stripe webhooks hit your server, then you update Adapty or RevenueCat. Use one stable user ID across web and app so mapping never breaks. Add a short delay before hard revokes because webhooks and user sessions rarely line up. In app, show a pending state with a manual retry and retry automatically in the background. Send a silent push on status change and run a daily reconciliation for disputes and partial refunds.
We held revokes for 4 minutes after the first webhook. During that window we show a banner and a retry button. Most false negatives clear in under 2 minutes. Support tickets dropped because the app explains what is going on.
For chargebacks we run a nightly job using Stripe Sigma to list disputes, join by customer ID, and push entitlement changes to Adapty. That caught edge cases where the webhook missed due to a deploy. Fixed random access leaks.
Pending banner plus retry works fine. People do not panic if you explain it.