Technical

RevenueCat Integration: From Zero to Subscriptions in Hours

Youcef February 4, 2026

RevenueCat Integration: From Zero to Subscriptions in Hours

I’ve shipped 3 apps with RevenueCat. Each time, the integration gets faster.

Muse Otter took 4 hours from zero to working subscriptions on iOS and Android.

Here’s how.


Why RevenueCat

Before RevenueCat, I spent weeks on StoreKit 1, then StoreKit 2, then Google Billing Library. Different APIs. Different edge cases. Different headaches.

RevenueCat gives you:

WhatWhy It Matters
One SDKSame code for iOS, Android, and Web
Receipt validationNo server-side code needed
Subscription stateHandles renewals, cancellations, grace periods
WebhooksReal-time sync to your backend
AnalyticsRevenue, churn, LTV out of the box

The math: 2 weeks of native implementation vs 4 hours with RevenueCat. Easy choice.


The Architecture

Here’s how Muse Otter handles subscriptions:

┌─────────────┐   Purchase    ┌─────────────┐
│ Flutter App │──────────────►│  RevenueCat │
│             │               │             │
│ purchases_  │   Entitle-    │  (handles   │
│ flutter     │◄──────────────│   stores)   │
└──────┬──────┘    ments      └──────┬──────┘
       │                             │
       │ Read from                   │ Webhook
       │ Firestore                   │ events
       ▼                             ▼
┌─────────────────────────────────────────────┐
│                 FIRESTORE                    │
│                                              │
│  users/{userId}                              │
│  └── subscriptionStatus: free | pro | lifetime │
│                                              │
│         ══ Single source of truth ══         │
└─────────────────────────────────────────────┘

Key insight: The app reads subscription status from Firestore, not RevenueCat. The webhook keeps Firestore in sync.


Step 1: RevenueCat Setup (30 min)

Dashboard Configuration

  1. Create app in RevenueCat dashboard
  2. Add products:
    • monthly_10 — $9.99/month
    • yearly_80 — $79.99/year
  3. Create entitlement: Muse Otter Pro
  4. Create offering: default
  5. Attach products to offering

App Store / Play Store

Set up products in App Store Connect and Google Play Console. Match the IDs exactly.


Step 2: Flutter Integration (1 hour)

Install SDK

# pubspec.yaml
dependencies:
  purchases_flutter: ^9.10.0

Initialize

Future<void> initRevenueCat() async {
  await Purchases.setLogLevel(LogLevel.debug);

  PurchasesConfiguration config;
  if (Platform.isIOS) {
    config = PurchasesConfiguration('appl_your_api_key');
  } else {
    config = PurchasesConfiguration('goog_your_api_key');
  }

  await Purchases.configure(config);
}

Login User

Future<void> loginUser(String firebaseUid) async {
  await Purchases.logIn(firebaseUid);
}

Important: Use your Firebase UID as the RevenueCat user ID. This links everything together.

Fetch Offerings

Future<Offerings?> getOfferings() async {
  try {
    return await Purchases.getOfferings();
  } catch (e) {
    logger.e('Failed to fetch offerings: $e');
    return null;
  }
}

Make Purchase

Future<bool> purchasePackage(Package package) async {
  try {
    await Purchases.purchasePackage(package);
    return true;
  } on PurchasesErrorCode catch (e) {
    if (e != PurchasesErrorCode.purchaseCancelledError) {
      logger.e('Purchase failed: $e');
    }
    return false;
  }
}

Step 3: Firebase Webhook (2 hours)

This is where Firestore becomes the source of truth.

Cloud Function

import { onRequest } from 'firebase-functions/v2/https';
import { getFirestore } from 'firebase-admin/firestore';

export const revenueCatWebhook = onRequest(
  { region: 'europe-west1', secrets: ['REVENUECAT_WEBHOOK_SECRET'] },
  async (req, res) => {
    // Verify authorization
    const authHeader = req.headers.authorization;
    if (authHeader !== process.env.REVENUECAT_WEBHOOK_SECRET) {
      res.status(401).send('Unauthorized');
      return;
    }

    const event = req.body;
    const userId = event.app_user_id;
    const eventType = event.type;

    // Map event to subscription status
    let newStatus: string | null = null;

    switch (eventType) {
      case 'INITIAL_PURCHASE':
      case 'RENEWAL':
      case 'UNCANCELLATION':
        newStatus = 'pro';
        break;
      case 'EXPIRATION':
        newStatus = 'free';
        break;
      // CANCELLATION: no change (user keeps access until expiry)
      // BILLING_ISSUE: no change (grace period)
    }

    if (newStatus) {
      const userRef = getFirestore().doc(`users/${userId}`);
      const userDoc = await userRef.get();

      // Don't override protected statuses
      const currentStatus = userDoc.data()?.subscriptionStatus;
      if (currentStatus === 'donation' || currentStatus === 'lifetime') {
        res.status(200).send('Protected status - no change');
        return;
      }

      await userRef.update({
        subscriptionStatus: newStatus,
        subscriptionUpdatedAt: new Date(),
      });
    }

    res.status(200).send('OK');
  }
);

Configure Webhook in RevenueCat

  1. Go to Project Settings → Integrations → Webhooks
  2. Add your Cloud Function URL
  3. Set authorization header
  4. Select events: INITIAL_PURCHASE, RENEWAL, EXPIRATION, CANCELLATION, UNCANCELLATION

Step 4: App Reads from Firestore (30 min)

Riverpod Provider

@riverpod
Stream<bool> isPro(IsProRef ref) {
  final user = ref.watch(currentUserProvider);
  if (user == null) return Stream.value(false);

  return FirebaseFirestore.instance
    .doc('users/${user.uid}')
    .snapshots()
    .map((doc) {
      final status = doc.data()?['subscriptionStatus'] ?? 'free';
      return status != 'free';
    });
}

Gate Pro Features

class ProGate extends ConsumerWidget {
  final Widget child;
  final Widget fallback;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final isPro = ref.watch(isProProvider);

    return isPro.when(
      data: (pro) => pro ? child : fallback,
      loading: () => fallback,
      error: (_, __) => fallback,
    );
  }
}

The Patterns That Work

1. Firestore as Source of Truth

Don’t check RevenueCat SDK every time. Write status to Firestore once, read from there always.

Why: Faster, works offline, one source of truth.

2. Protected Statuses

Some users get free access (testers, contest judges, supporters). Protect them:

if (currentStatus === 'donation' || currentStatus === 'lifetime') {
  // Don't change anything
  return;
}

3. Login with Firebase UID

Always use the same user ID in RevenueCat and Firebase. Makes webhooks trivial.

4. Soft Paywall First

Don’t block immediately. Show value, then ask for money:

Free: 20 messages/day
Pro: Unlimited

Users hit the limit organically. Then the paywall feels fair.


Results

Muse Otter subscription system:

  • 4 hours to integrate
  • 100% event coverage via webhooks
  • Works offline (reads from Firestore cache)
  • Protected statuses for manual grants
  • A/B testable via Firebase Remote Config

Try It

RevenueCat’s free tier handles up to $2.5k/month. That’s enough to validate your product.

Don’t build subscription infrastructure. Just ship.

The best payment system is the one you don’t have to maintain.

Start Reflecting. The Otter Will Do the Rest.

30 seconds to capture. 7 minutes to review. A lifetime of patterns to discover.

Also on iPad with Apple Pencil

Free to start. No credit card required.