RevenueCat

Build With RevenueCat

Build a customized mobile subscription business with RevenueCat. We do the heavy lifting of normalizing subscribers from any source and maintain a single source of truth for subscription status, so you can get back to building your app.

RevenueCat is a powerful, secure, reliable, and free to use in-app purchase server with global support. All you need to get started is an API key.

Quickstart

Get up and running with mobile subscriptions

This guide will walk you through a practical example to get you up and running with subscriptions and the Purchases SDK with only a few lines of code.

Register Your App

Navigate to the the RevenueCat dashboard and add a new app from the dropdown.

One app for all platforms

A single app in RevenueCat supports all platforms.

Configure Your Products

In-app products still need to be configured in App Store Connect and the Play Console, but RevenueCat has a unique way of unlocking access and displaying products through Entitlements and Offerings.

Entitlements are the level of access that a user is "entitled" to after purchasing a specific product and Offerings are a simple way for you to organize your in-app products and configure them remotely. Together this simplifies your code and enables you to change products without an app update.

See Configuring Products for how to configure and leverage Entitlements and Offerings.

Install the Purchases SDK

Purchases is our SDK that correctly implements purchases and subscriptions across platforms while syncing tokens with the RevenueCat server.

Configure Purchases

Only use your Public SDK Key to configure Purchases

You can get your Public SDK key from app settings in the dashboard.

You should only configure Purchases once, usually on app launch. The same instance is shared throughout your app by accessing the .shared instance in the SDK.

Be sure you're configuring Purchases with your Public SDK Key only. You can read more about the different API keys available in our Authentication guide.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  
    Purchases.debugLogsEnabled = true
    Purchases.configure(withAPIKey: "public_sdk_key")
  
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  	// Override point for customization after application launch.
    
  	RCPurchases.debugLogsEnabled = YES;
  	[RCPurchases configureWithAPIKey:@"public_sdk_key"];
    
  	return YES;
}
class MainApplication: Application() {

    override fun onCreate() {
        super.onCreate()
        Purchases.debugLogsEnabled = true
        Purchases.configure(this, "public_sdk_key")
    }

}

public class MainApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Purchases.setDebugLogsEnabled(true);
        Purchases.configure(this, "public_sdk_key");
    }

}
Future<void> initPlatformState() async {
  Purchases.setDebugLogsEnabled(true);
  await Purchases.setup("public_sdk_key");
}
export default class App extends React.Component {
  
  componentDidMount() {
    Purchases.setDebugLogsEnabled(true);
    Purchases.setup("public_sdk_key");
  }
  
}
document.addEventListener("deviceready", onDeviceReady, false);

function onDeviceReady() {
    Purchases.setDebugLogsEnabled(true);
    Purchases.setup("public_sdk_key");
}
See Unity installation instructions https://docs.revenuecat.com/docs/unity

If you're looking to use RevenueCat alongside your existing purchase code, check out our guide on Observer Mode.

Usage with User IDs

If you have a user authentication system in your app, you can use provide a user identifier to Purchases right at the time of configuration or at a later time with a call to identify. You can learn more learn more on our guide on identifying users.

Displaying Available Products

Purchases will automatically fetch the configured Offerings and get the product information from Apple or Google. This means when users launch your purchase screen, products will already be loaded.

Below is an example of fetching entitlements and launching a paywall screen. See our guide on Displaying Products for more information and best practices.

Purchases.shared.offerings { (offerings, error) in
	if offerings {
	  showPaywall(offerings?.current)
  }
}
[[RCPurchases sharedPurchases] offeringsWithCompletionBlock:^(RCOfferings *offerings, NSError *error) {
  if (offerings) {
		[self showPaywallWithOffering:offerings.current];
  } else if (error) {
    // optional error handling
  }
}];
Purchases.sharedInstance.getOfferingsWith(
	onError = { errror ->
    /* Optional error handling */ 
  },
  onSuccess = { offerings ->  
  	showPaywall(offerings.current)
	}
}
Purchases.getSharedInstance().getOfferings(new ReceiveOfferingsListener() {
    @Override
    public void onReceived(@Nullable Map<String, Entitlement> offerings) {
        showPaywall(offerings.current);
    }
  
	  @Override
    public void onError(@NonNull PurchasesError error) {
        /* Optional error handling */ 
    }
});
try {
  Offerings offerings = await Purchases.getOfferings();
  if (offerings.current != null) {
    showPaywall(offerings.current);
  }
} on PlatformException catch (e) {
	// optional error handling
}
try {
  const offerings = await Purchases.getOfferings();
  if (offerings.current !== null) {  
	  showPaywall(offerings.current);
  }
} catch (e) {

}
func displayUpsellScreen() {
  Purchases.getOfferings(
      offerings => {
        if (offerings.current !== null) {  
          showPaywall(offerings.current);
        }
      },
      error => {

      }
  );
}
var purchases = GetComponent<Purchases>();
purchases.GetOfferings((offerings, error) =>
{
  if (error != null) {
    LogError(error);
  } else if (offerings.Current != null {
    showPaywall(offerings.Current);
  }
});

Since the SDK pre-fetches the available products, the completion block to get offerings won't need to make a network request in most cases. This creates a seamless onboarding experience that results in less drop-off, happier users, and more revenue for you.

Make a Purchase

When it comes time to make a purchase, Purchases has a simple method, purchasePackage, that takes a package from the fetched Offering and purchases the underlying product with Apple or Google.

The code sample below shows the process of purchasing a package and confirming it unlocks the "your_entitlement_id" content. More detail about the purchasePackage method can be found in our guide on Making Purchases.

Purchases.shared.purchasePackage(package) { (transaction, purchaserInfo, error, userCancelled) in
    if purchaserInfo?.entitlements.all["your_entitlement_id"]?.isActive == true {
        // Unlock that great "pro" content
    }
})
[[RCPurchases sharedPurchases] purchasePackage:package withCompletionBlock:^(SKPaymentTransaction *transaction, RCPurchaserInfo *purchaserInfo, NSError *error, BOOL cancelled) {
    if (purchaserInfo.entitlements.all[@"your_entitlement_id"].isActive) {
    // User is "premium"
	}
}];
Purchases.sharedInstance.purchasePackageWith(
  this,
  package,
  onError = { error, userCancelled -> /* No purchase */ },
  onSuccess = { product, purchaserInfo ->
    if (purchaserInfo.entitlements["my_entitlement_identifier"]?.isActive == true) {
    // Unlock that great "pro" content
  }
})
Purchases.getSharedInstance()purchasePackage(
    this,
    package,
    new MakePurchaseListener() {
        @Override
        public void onCompleted(@NonNull Purchase purchase, @NonNull PurchaserInfo purchaserInfo) {
            if (purchaserInfo.getEntitlements().get("my_entitlement_identifier").isActive()) {
              // Unlock that great "pro" content
            }
        }

        @Override
        public void onError(@NonNull PurchasesError error, Boolean userCancelled) {
          // No purchase
        }
    }
);
try {
  PurchaserInfo purchaserInfo = await Purchases.purchasePackage(package);
  var isPro = purchaserInfo.entitlements.all["my_entitlement_identifier"].isActive;
  if (isPro) {
    // Unlock that great "pro" content
  }
} on PlatformException catch (e) {
  var errorCode = PurchasesErrorHelper.getErrorCode(e);
  if (errorCode != PurchasesErrorCode.purchaseCancelledError) {
    showError(e);  	          
  }
}
// Using packages
try {
  const purchaseMade = await Purchases.purchasePackage(package);
  if (typeof purchaseMade.purchaserInfo.entitlements.active.my_entitlement_identifier !== "undefined") {
    // Unlock that great "pro" content
  }
} catch (e) {
  if (!e.userCancelled) {
  	showError(e);
  }
}

// Note: if you are using purchaseProduct to purchase Android In-app products, an optional third parameter needs to be provided when calling purchaseProduct. You can use the package system to avoid this
await Purchases.purchaseProduct("product_id", null, Purchases.PURCHASE_TYPE.INAPP);
Purchases.purchasePackage(package, ({ productIdentifier, purchaserInfo }) => {
    if (typeof purchaserInfo.entitlements.active.my_entitlement_identifier !== "undefined") {
      // Unlock that great "pro" content
    }
  },
  ({error, userCancelled}) => {
    // Error making purchase
  }
);

// Note: if you are using purchaseProduct to purchase Android In-app products, an optional third parameter needs to be provided when calling purchaseProduct. You can use the package system to avoid this.

Purchases.purchaseProduct("product_id", ({ productIdentifier, purchaserInfo }) => {
}, ({error, userCancelled}) => {
    // Error making purchase
}, null, Purchases.PURCHASE_TYPE.INAPP);
Purchases purchases = GetComponent<Purchases>();
purchases.PurchasePackage(package, (productIdentifier, purchaserInfo, userCancelled, error) =>
{
  if (purchaserInfo.Entitlements.Active.ContainsKey("my_entitlement_identifier")) {
    // Unlock that great "pro" content
  }
});

purchasePackage handles the underlying framework interaction and automatically validates purchases with Apple or Google through our secure servers. This helps reduce in-app purchase fraud and decreases the complexity of your app. Receipt tokens are stored remotely and always kept up-to-date by RevenueCat. Note that a physical device and sandbox account are required for testing purchases.

Get Subscription Status

Purchases makes it easy to check what active subscriptions the current user has. This can be done by checking if a specific entitlement is active, or by checking if the active entitlements contains a specific entitlement Id.

If you're not using entitlements (you should be) you can check the array of active subscriptions to see what products Ids from iTunes Connect or Play Store it contains.

Purchases.shared.purchaserInfo { (purchaserInfo, error) in
    if purchaserInfo?.entitlements.all["your_entitlement_id"]?.isActive == true {
        // User is "premium"
    }
}
[[RCPurchases sharedPurchases] purchaserInfoWithCompletionBlock:^(RCPurchaserInfo * purchaserInfo, NSError * error) {
        
    if (purchaserInfo.entitlements.all[@"your_entitlement_id"].isActive) {
    // User is "premium"
	}
}];
Purchases.sharedInstance.getPurchaserInfoWith({ error -> /* Optional error handling */ }) { purchaserInfo ->
  if (purchaserInfo.entitlements["my_entitlement_identifier"]?.isActive == true) {
    // Grant user "pro" access
  }
}
Purchases.getSharedInstance().getPurchaserInfo(new ReceivePurchaserInfoListener() {
  @Override
  public void onReceived(@NonNull PurchaserInfo purchaserInfo) {
    if (purchaserInfo.getEntitlements().get("my_entitlement_identifier").isActive()) {
      // Grant user "pro" access
    }
  }
  
  @Override
  public void onError(@NonNull PurchasesError error) {

  }
});
try {
  PurchaserInfo purchaserInfo = await Purchases.getPurchaserInfo();
  if (purchaserInfo.entitlements.all["my_entitlement_identifier"].isActive) {
    // Grant user "pro" access
  }
} on PlatformException catch (e) {
  // Error fetching purchaser info
}
try {
  const purchaserInfo = await Purchases.getPurchaserInfo();
  if(typeof purchaserInfo.entitlements.active.my_entitlement_identifier !== "undefined") {
    // Grant user "pro" access
  }
} catch (e) {
 // Error fetching purchaser info
}
Purchases.getPurchaserInfo(
  info => {
    const isPro = typeof purchaserInfo.entitlements.active.my_entitlement_identifier !== "undefined";
  },
  error => {
    // Error fetching purchaser info
  }
);
var purchases = GetComponent<Purchases>();
purchases.GetPurchaserInfo((info, error) =>
{
   if (purchaserInfo.Entitlements.Active.ContainsKey("my_entitlement_identifier")) {
    // Unlock that great "pro" content
  }
});

You can use this method whenever you need to get the latest status, and it's safe to call this repeatedly throughout the lifecycle of your app. Purchases automatically caches the latest purchaserInfo whenever it updates so in most cases this method is pulling from the cache and is run very fast.

It's typical to call this method when deciding which UI to show the user and whenever the user performs and action that requires a certain entitlement level.

You can access a lot more information about a subscription than simply whether it's active or not. See our guide on Subscription Status to learn if subscription is set to renew, if there's an issue detected with the user's credit card, and more.

Restoring Purchases

Restoring purchases is a mechanism by which your user can restore their in-app purchases, reactivating any content that had previously been purchased from the same store account (Apple or Google).

If two different App User IDs try to restore transactions from the same underlying store account (Apple or Google) RevenueCat will create an alias between the two App User IDs and count them as the same user going forward.

Purchases.shared.restoreTransactions { (purchaserInfo, error) in
    //... check purchaserInfo to see if entitlement is now active
}
[[RCPurchases sharedPurchases] restoreTransactionsWithCompletionBlock:^(RCPurchaserInfo *purchaserInfo, NSError *error) {
    //... check purchaserInfo to see if entitlement is now active
}];
Purchases.sharedInstance.restorePurchasesWith(::showError) { purchaserInfo ->
    //... check purchaserInfo to see if entitlement is now active
}
Purchases.getSharedInstance().restorePurchases(new ReceivePurchaserInfoListener() {
	@Override
	public void onReceived(@android.support.annotation.Nullable PurchaserInfo purchaserInfo, @android.support.annotation.Nullable PurchasesError error) {
    //... check purchaserInfo to see if entitlement is now active 	
  }
});
try {
  PurchaserInfo restoredInfo = await Purchases.restoreTransactions();
  // ... check restored purchaserInfo to see if entitlement is now active
} on PlatformException catch (e) {
  // Error restoring purchases
}
try {
  const restore = await Purchases.restoreTransactions();
  // ... check restored purchaserInfo to see if entitlement is now active
} catch (e) {

}
Purchases.restoreTransactions(
  info => {
    //... check purchaserInfo to see if entitlement is now active
  },
  error => {
    // Error restoring purchases
  }
);
var purchases = GetComponent<Purchases>();
purchases.RestoreTransactions((info, error) =>
{
    //... check purchaserInfo to see if entitlement is now active
});

Listening For Purchaser Info Updates

Since Purchases SDK works seamlessly on any platform, a user's purchase info may change from a variety of sources. You can respond to any changes in purchaser info by conforming to an optional delegate method, didReceivePurchaserInfo:. This will fire whenever we receive a change in purchaser info and you should expect it to be called at launch and throughout the life of the app.

Depending on your app, it may be sufficient to ignore the delegate and simply handle changes to purchaser information the next time your app is launched.

extension AppDelegate: PurchasesDelegate {
  func purchases(_ purchases: Purchases, didReceiveUpdated purchaserInfo: Purchases.PurchaserInfo) {
      // handle any changes to purchaserInfo
  }
}
- (void)purchases:(nonnull RCPurchases *)purchases didReceiveUpdatedPurchaserInfo:(nonnull RCPurchaserInfo *)purchaserInfo {
    // handle any changes to purchaserInfo
}
class UpsellActivity : AppCompatActivity(), UpdatedPurchaserInfoListener {
		override fun onReceived(purchaserInfo: PurchaserInfo) {
        // handle any changes to purchaserInfo
    }
}
public class UpsellActivity extends AppCompatActivity implements UpdatedPurchaserInfoListener {
		@Override
    public void onReceived(PurchaserInfo purchaserInfo) {
        // handle any changes to purchaserInfo
    }
}
Purchases.addPurchaserInfoUpdateListener((purchaserInfo) => {
  // handle any changes to purchaserInfo
});
Purchases.addPurchaserInfoUpdateListener(info => {
	// handle any changes to purchaserInfo
});
// subscribe to the window event onPurchaserInfoUpdated to get any changes that happen in the purchaserInfo
window.addEventListener("onPurchaserInfoUpdated", onPurchaserInfoUpdated, false);

//...

onPurchaserInfoUpdated: function(info) {
    // handle any changes to purchaserInfo
}


public override void PurchaserInfoReceived(Purchases.PurchaserInfo purchaserInfo)
{
    // handle any changes to purchaserInfo
}

You've done it!

That's all there is to it! You have now implemented a fully featured subscription purchasing system without spending a month writing server code. Congrats!

Sample Apps

To download complete examples of integrating the Purchases SDK - head over to our to our sample app resources.

View Samples

Next Steps

Updated about a month ago

Quickstart


Get up and running with mobile subscriptions

Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.