▲ 6 r/Firebase
Flutter + Firebase + RevenueCat: how do you keep subscription state in sync across app, backend and Apple? Are we overcomplicating this?
We're about to ship a Flutter (iOS-first) app and I'd like some inputs on our subscription and auth setup from people who've done this in production.
Current setup
- Auth: Firebase Anonymous Auth by default, so the core features work with no sign-up. Sign in with Apple is optional and never required for a trial or entitlement.
- Purchases: RevenueCat handles the paywall and purchase flow. The 7-day trial is Apple's intro offer.
- Source of truth: our own backend (Firebase Functions, Node/Express). Every AI request is gated server-side, and the client never decides access on its own. The backend reads the entitlement from Firestore.
- Ownership: a subscription belongs to Apple's
originalTransactionId, not to a Firebase UID. Each one has asubscription_owners/{originalTransactionId}doc pointing at the current UID.
How we keep things in sync (this is the part I'd like opinions on)
- The RevenueCat webhook updates Firestore. It's idempotent by event ID and ignores stale events by timestamp.
- App Store Server Notifications V2 is a fallback for owners that RevenueCat doesn't manage.
- The client caches the entitlement in the Keychain with a 6h TTL and force-refreshes on app resume if the cache is stale.
- The backend also polls the App Store Server API on the request path. It checks every 6h normally, every 15 minutes in the last day of a period, and every couple of minutes right after expiry. There's a timeout and a circuit-breaker pause on failures.
- Restores under a different UID transfer ownership. We ignore RevenueCat's TRANSFER events on purpose, because the public SDK key lets anyone post a receipt under any app user ID.
- When a guest signs in with Apple, we merge the guest into the Apple account server-side: subscription owners, progress and history. Then we call
syncPurchases()under the new UID.
What feels painful
- We have three sources (RevenueCat, Apple notifications, Apple polling) plus a client cache, and they can disagree for a few seconds or minutes. Most of our bugs have been races between them, like a webhook arriving before a restore, or a merge happening mid-purchase.
- Guest to Apple-account merging and UID changes are where most of the complexity comes from.
- Entitlement logic is split between RevenueCat's
CustomerInfoon the client and our own Firestore record on the server. We chose this so a modified client can't unlock anything.
Questions
- Is the client-cache + webhook + server-poll setup normal, or is it overkill? Do most people just trust
CustomerInfoon the client, or use RevenueCat's REST API as the server-side check instead of polling Apple? - Has anyone used the official RevenueCat Firebase extension and found it good enough? Does it handle UID changes and restores cleanly?
- For anonymous to real account upgrades, do you use Firebase
linkWithCredential, or merge server-side like we do? We avoided linking because ofinvalid-credentialissues with Apple and nonces. - Is there a library or pattern that keeps Firebase Auth UID and subscription ownership aligned without hand-written merge logic?
- What are we likely missing?
Happy to share more detail on any piece. Thanks!
u/Open-Put3327 — 14 days ago