Skip to content
Flutter+Firebase + RevenueCat
Stack Integration

Flutter + Firebase + RevenueCat: Mobile Subscriptions

RevenueCat abstracts the complexity of App Store and Google Play subscription billing, while Firebase handles user auth and real-time data — together they cover the full mobile subscription stack.

Use Cases
  1. Cross-platform subscription billing via App Store and Google Play
  2. Entitlement checking to gate premium features in Flutter
  3. Subscription status synced to Firestore for backend access control
  4. Paywall A/B testing and subscription analytics via RevenueCat dashboard
Implementation

Initialize RevenueCat's Purchases SDK alongside Firebase in main(). Use Purchases.getCustomerInfo() to check entitlements before rendering premium content. Sync RevenueCat customer ID with Firebase Auth UID via RevenueCat's Firebase extension or a Cloud Function webhook — this lets your backend verify subscription status server-side. Never rely solely on client-side entitlement checks for server-side resource access.

In detail

How the pieces connect

Three layers run in different places. Flutter is the client, holding the UI and the purchases_flutter SDK. RevenueCat is a billing proxy: the SDK calls Purchases.purchasePackage(), which routes the actual transaction through StoreKit on iOS and Google Play Billing on Android, then RevenueCat validates the receipt and tracks the subscription. Firebase Auth owns identity, and Firestore holds your app data.

The glue is the app user ID. Call Purchases.logIn(firebaseUser.uid) right after Firebase Auth resolves, so RevenueCat's appUserID equals your Firebase UID. RevenueCat then fires server-to-server webhooks (INITIAL_PURCHASE, RENEWAL, CANCELLATION, EXPIRATION) to a Cloud Function, which writes entitlement state to that user's Firestore document. The client reads getCustomerInfo().entitlements.active for instant UI gating; the server reads Firestore for the trustworthy copy.

Production gotchas

The non-obvious failures cluster around identity and trust.

  • Anonymous IDs leak. If you call Purchases.configure() before login, RevenueCat mints an anonymous $RCAnonymousID. Purchases made then attach to that ghost, not the Firebase UID. Always logIn(uid) after auth, and logOut() on sign-out so the next user doesn't inherit entitlements.
  • Webhook auth. Set an Authorization header on the RevenueCat webhook and verify it inside the Cloud Function; an unauthenticated HTTPS function will accept forged status writes otherwise.
  • Idempotency. RevenueCat can resend a webhook. Key your Firestore write on the event ID so a retry doesn't double-process a renewal.
  • Cloud Function cold starts can delay the Firestore write past the moment the client already shows premium content from getCustomerInfo() — design the UI to tolerate brief disagreement.

Client checks vs. server checks

getCustomerInfo() is fine for hiding a button or showing a paywall — worst case a user briefly sees UI they can't really use. It is not an access-control boundary. The client-side CustomerInfo is cached locally and trivially manipulable on a rooted or jailbroken device.

Anything that returns paid data or runs a paid operation must check Firestore server-side. Encode the rule in Firestore Security Rules — for example, allow reads on a premium collection only when the requesting user's profile document has entitlements.pro == true, a value only the webhook-backed Cloud Function (running with Admin privileges) can set. For paid backend work behind a callable function, re-read entitlement state from Firestore inside the function rather than trusting a flag passed from the client.

When this combo fits — and when it doesn't

It fits when you sell recurring access inside an iOS or Android app and want one entitlement model across both stores without writing receipt-validation code twice. RevenueCat handles store quirks (grace periods, billing retries, refunds) and gives you cohort and churn analytics for free; Firebase covers auth, data, and the serverless functions to react to billing events.

It's the wrong tool for purely web payments — App Store and Play Billing only apply to native app purchases, so a web checkout still needs Stripe or similar. It's also overhead if you only ever sell one non-renewing purchase; a single in-app product through StoreKit/Play Billing directly may not justify the extra dependency. For genuine subscriptions across two platforms, though, it removes the most error-prone layer.

Other integration guidesView all →
Related

Need this built?