Firebase Apps

Integrate your Firebase app with RevenueCat

What is RevenueCat?

RevenueCat is a scalable backend for in-app subscriptions and purchases. With RevenueCat, you can build and manage your mobile app business without having to set up or maintain any purchase infrastructure. Here you can read more about how RevenueCat fits into your app.

🚧

Deprecated

We recommend checking out our official Firebase integration for a more seamless integration between RevenueCat and Firebase.

Firebase is a popular backend-as-a-service by Google for building mobile apps.

Keeping User IDs In Sync

Firebase has a well documented authentication mechanism that works with Email/Password, Github, Google Sign-In, anonymous authentication and more.

It's simple to keep your user IDs from Firebase in sync with the RevenueCat backend by listening to authentication state change notifications posted by Firebase.

Below are practical examples of setting this up in your application:

iOS

import Firebase
import RevenueCat

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    // Configure Purchases before Firebase
    Purchases.configure(withAPIKey: "public_sdk_key")
    Purchases.shared.delegate = self

    // Configure Firebase
    FirebaseApp.configure()

    // Add state change listener for Firebase Authentication
    Auth.auth().addStateDidChangeListener { (auth, user) in

        if let uid = user?.uid {

            // identify Purchases SDK with new Firebase user
            Purchases.shared.logIn(uid, { (info, created, error) in
                if let error {
                    print("Sign in error: \(error.localizedDescription)")
                } else {
                    print("User \(uid) signed in")
                }
            })
        }
    }

    return true
}

// MARK: - Purchases delegate (optional)
func purchases(_ purchases: Purchases, receivedUpdated customerInfo: CustomerInfo) {

    // You can optionally post to the notification center whenever
    // customer info changes.

    // You can subscribe to this notification throughout your app
    // to refresh tableViews or change the UI based on the user's
    // subscription status
    let notificationName = Notification.Name(rawValue: "com.RevenueCat.customerInfoUpdatedNotification")
    NotificationCenter.default.post(name: notificationName, object: nil)
}

Android

class MainApplication: Application() {

    private lateinit var auth: FirebaseAuth
      
    override fun onCreate() {
        super.onCreate()
          
        // Configure Purchases
        Purchases.configure(this, "public_sdk_key")
    }
}
  
class MainActivity: Activity() {
    public override fun onCreate() {

        // Initialize Firebase Auth
        firebaseAuth = FirebaseAuth.getInstance()  
    }

    public override fun onStart() {
        super.onStart()
        
        val authStateListener = FirebaseAuth.AuthStateListener { auth ->
        
            val user = auth.currentUser

            if(user != null) {

                // identify Purchases with new Firebase user
                Purchases.sharedInstance.loginWith("my_app_user_id", ::showError) { customerInfo, created ->
                    Log.d(TAG, "User signed in.")
                }
                
            }
        }
    
        firebaseAuth.addAuthStateListener(authStateListener)
    }
}



// Purchases Change Listener (Optional)
class UpsellActivity : AppCompatActivity(), UpdatedCustomerInfoListener {
		override fun onReceived(customerInfo: CustomerInfo?) {
        
    // You can optionally react to any changes in customer info
    // such as updating the UI throughout your app
    }
}

📘

Always test authentication in your own app and make sure everything is working as expected. Since every app is different, the above helpers can get you started but may need to be modified to work with the authentication flow specific to your app.

Handling Events With Cloud Functions

Firebase Cloud Functions are a great way to run server-side code without having to manage your own server. This makes them excellent for things like catching webhooks.

Below is an example that uses a combination of HTTP triggers and Pub/Sub triggers to handle webhook events from RevenueCat.

The URL of the incomingWebhook function should be pasted into the RevenueCat dashboard along with a secret auth key that the function will use to verify it is actually RevenueCat that's sending the event.

Setup

Function Code

const functions = require('firebase-functions');
const {PubSub} = require('@google-cloud/pubsub');

// Your Firebase project ID
const projectId = '<YOUR_FIREBASE_PROJECT_ID>';

// Instantiates a client
const pubsubClient = new PubSub({
  projectId: projectId,
});


function isAuthorized(req) {
  // Check authorization header
  if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) {
    return false;
  }
  const authToken = req.headers.authorization.split('Bearer ')[1];
  if (authToken !== '<YOUR_REVENUECAT_WEBHOOK_AUTH_TOKEN>') {
    return false;
  }

  return true;
}


// Respond to incoming message
exports.incomingWebhook = functions.https.onRequest((req, res) => {
  
  // Only allow POST requests
  if (req.method !== 'POST') {
    return res.status(403).send('Forbidden');
  }

  // Make sure the authorization key matches what we
  // set in the RevenueCat dashboard
  if (!isAuthorized(req)) {
    return res.status(401).send('Unauthorized'); 
  }

  const event = req.body.event;
  
  var topic = '';


  // Check for the event types that you want to respond to
  // You may only need a subset of these events 
  switch(event.type) {
    case 'INITIAL_PURCHASE':
      topic = 'rc-initial-purchase';
      break;
    case 'NON_RENEWING_PURCHASE':
      topic = 'rc-non-renewing-purchase';
      break;
    case 'RENEWAL':
      topic = 'rc-renewal';
      break;
    case 'PRODUCT_CHANGE':
      topic = 'rc-product-change';
      break;
    case 'CANCELLATION':
      topic = 'rc-cancellation';
      break;
    case 'BILLING_ISSUE':
      topic = 'rc-billing-issue';
      break;
    case 'SUBSCRIBER_ALIAS':
      topic = 'rc-subscriber-alias';
      break;
    default:
      console.log('Unhandled event type: ', event.type);
      return res.sendStatus(200);
  }

  // Set the pub/sub data to the event body
  const dataBuffer = Buffer.from(JSON.stringify(event));

  // Publishes a message
  return pubsubClient.topic(topic)
    .publish(dataBuffer)
    .then(() => res.sendStatus(200))
    .catch(err => {
      console.error(err);
      res.sendStatus(500);
      return Promise.reject(err);
   });
});

exports.handleInitialPurchase = functions.pubsub.topic('rc-initial-purchase').onPublish((message, context) => {
  
  // Handle initial purchases of subscription products
  console.log('INITIAL_PURCHASE: ', message.json);
  return null;
});

exports.handleNonRenewingPurchase = functions.pubsub.topic('rc-non-renewing-purchase').onPublish((message, context) => {
  
  // Handle a non-subscription purchase
  console.log('NON_RENEWING_PURCHASE: ', message.json);
  return null;
});

exports.handleRenewal = functions.pubsub.topic('rc-renewal').onPublish((message, context) => {
  
  // Handle subscription renewal
  console.log('RENEWAL: ', message.json);
  return null;
});

exports.handleProductChange = functions.pubsub.topic('rc-product-change').onPublish((message, context) => {
  
  // Handle subscription product change
  console.log('PRODUCT_CHANGE: ', message.json);
  return null;
});

exports.handleCancellation = functions.pubsub.topic('rc-cancellation').onPublish((message, context) => {
  
  // Handle subscription cancellations. Note that
  // a subscription may still be active depending
  // on the cancellation type and you shouldn't
  // automatically cut off access.
  console.log('CANCELLATION: ', message.json);
  return null;
});

exports.handleBillingIssue = functions.pubsub.topic('rc-billing-issue').onPublish((message, context) => {
  
  // Handle billing issues with subscription renewals
  console.log('BILLING_ISSUE: ', message.json);
  return null;
});

exports.handleSubscriberAlias = functions.pubsub.topic('rc-subscriber-alias').onPublish((message, context) => {
  
  // Handle subscriber alias events
  console.log('SUBSCRIBER_ALIAS: ', message.json);
  return null;
});

📘

Write idempotent functions

Firebase PubSub functions should be idempotent, that is, they should produce the same result even if they are called multiple times. See Firebase guides for more information