u/Open-Put3327

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 a subscription_owners/{originalTransactionId} doc pointing at the current UID.

How we keep things in sync (this is the part I'd like opinions on)

  1. The RevenueCat webhook updates Firestore. It's idempotent by event ID and ignores stale events by timestamp.
  2. App Store Server Notifications V2 is a fallback for owners that RevenueCat doesn't manage.
  3. The client caches the entitlement in the Keychain with a 6h TTL and force-refreshes on app resume if the cache is stale.
  4. 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.
  5. 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.
  6. 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 CustomerInfo on the client and our own Firestore record on the server. We chose this so a modified client can't unlock anything.

Questions

  1. Is the client-cache + webhook + server-poll setup normal, or is it overkill? Do most people just trust CustomerInfo on the client, or use RevenueCat's REST API as the server-side check instead of polling Apple?
  2. Has anyone used the official RevenueCat Firebase extension and found it good enough? Does it handle UID changes and restores cleanly?
  3. For anonymous to real account upgrades, do you use Firebase linkWithCredential, or merge server-side like we do? We avoided linking because of invalid-credential issues with Apple and nonces.
  4. Is there a library or pattern that keeps Firebase Auth UID and subscription ownership aligned without hand-written merge logic?
  5. What are we likely missing?

Happy to share more detail on any piece. Thanks!

reddit.com
u/Open-Put3327 — 14 days ago