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.

Get Started    API Reference

Quickstart

Get up and running with mobile subscriptions

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

Register Your App

Navigate to 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

Once your in-app products have been configured in App Store Connect and the Google Play Console, RevenueCat provides a couple of features for unlocking access and displaying products: Entitlements and Offerings.

Entitlements are the level of access that a user is "entitled" to after purchasing a specific product, and Offerings is a simple way for you to organize your in-app products and configure them remotely. Together, these two features simplify your code and enable 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 the RevenueCat SDK that seamlessly implements purchases and subscriptions across platforms while syncing tokens with the RevenueCat server.

Configure Purchases

πŸ“˜

Use your public SDK key to configure Purchases

You can get your public SDK key from the 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.

See our guide on Configuring SDK for more information and best practices.

Make sure you configure 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.debugLogsEnabled = true;
       Purchases.configure(this, "public_sdk_key");
    }

}
Future<void> initPlatformState() async {
  await 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 planning to use RevenueCat alongside your existing purchase code, check out our guide on Observer Mode.

πŸ“˜

Configuring Purchases with User IDs

If you have a user authentication system in your app, you can provide a user identifier to Purchases at the time of configuration or at a later date with a call to identify. To learn more, check out our guide on Identifying Users.

Displaying Available Products

Purchases will automatically fetch the configured Offerings and retrieve the product information from Apple or Google. This means when users launch your purchase screen, available 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 let offerings = offerings {
      // Display current offering with offerings.current
  }
}
[[RCPurchases sharedPurchases] offeringsWithCompletionBlock:^(RCOfferings *offerings, NSError *error) {
  if (offerings) {
        // Display current offering with offerings.current
  } else if (error) {
    // optional error handling
  }
}];
Purchases.sharedInstance.getOfferingsWith(
    onError = { error ->
    /* Optional error handling */ 
  },
  onSuccess = { offerings ->  
    // Display current offering with offerings.current
    }
}
Purchases.getSharedInstance().getOfferings(new ReceiveOfferingsListener() {
    @Override
    public void onReceived(@Nullable Map<String, Entitlement> offerings) {
        // Display current offering with offerings.current
    }
  
      @Override
    public void onError(@NonNull PurchasesError error) {
        /* Optional error handling */ 
    }
});
try {
  Offerings offerings = await Purchases.getOfferings();
  if (offerings.current != null) {
    // Display current offering with offerings.current
  }
} on PlatformException catch (e) {
    // optional error handling
}
try {
  const offerings = await Purchases.getOfferings();
  if (offerings.current !== null) {  
      // Display current offering with offerings.current
  }
} catch (e) {

}
func displayUpsellScreen() {
  Purchases.getOfferings(
      offerings => {
        if (offerings.current !== null) {  
          // Display current offering with offerings.current
        }
      },
      error => {

      }
  );
}
var purchases = GetComponent<Purchases>();
purchases.GetOfferings((offerings, error) =>
{
  if (error != null) {
    LogError(error);
  } else if (offerings.Current != null {
    // Display current offering with offerings.current
  }
});

If your Offerings, products, or available packages are empty, it's due to some configuration issue in App Store Connect or the Play Console.

The most common reasons for this in App Store Connect are an out-of-date 'Paid Applications Agreement' or products not at least in the 'Ready To Submit' state. On Android this usually occurs when the app is not published on a closed track and a valid test user added.

You can find more info about trouble shooting this issue in our Help Center.

πŸ“˜

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

Make a Purchase

Purchases includes a simple method for facilitating purchases. The purchasePackage takes a package from the fetched Offering and processes the transaction 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 product IDs from iTunes Connect or Google 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 pulls from the cache and runs very fast.

It's typical to call this method when deciding which UI to show the user and whenever the user performs an 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

RevenueCat enables your users to restore their in-app purchases, reactivating any content that they previously 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 the Purchases SDK works seamlessly on any platform, changes to a user's purchase info may come from a variety of sources. You can respond to any changes in a user’s purchaser info by conforming to an optional delegate method, didReceivePurchaserInfo:. This will fire whenever the Purchases SDK receives an updated purchaser info object from calls to getPurchaserInfo(), purchasePackage(), purchaserProduct(), or restoreTransactions().

Purchaser Info updates are not pushed to your app from the RevenueCat backend, updates can only happen from an outbound network request to RevenueCat.

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 did it!

You have now implemented a fully-featured subscription purchasing system without spending a month writing server code. Congrats!

Sample Apps

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

View Samples

Next Steps

Updated 20 days 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.