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 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 in RevenueCat

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.

Setup your Entitlements

Entitlements are a simple way for you to organize your in-app products and configure them remotely. This simplifies the client side code and enables you to change products without an app update.

See Configuring Entitlements for how to configure and leverage entitlements.

πŸ“˜

Configure Products First

Before configuring entitlements, you should setup your in-app products in either App Store Connect or Play Developer Console.

A common and simple setup example is one entitlement with identifier pro, one offering monthly, with one product.

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) as soon as your app has a unique user id for your user. This can be when a user logs in if you have accounts or on launch if you can generate a random user identifier. 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", appUserID: "my_app_user_id")
  
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    RCPurchases.debugLogsEnabled = YES;
    [RCPurchases configureWithAPIKey:@"public_sdk_key" appUserID:@"my_app_user_id"];
    
    return YES;
}
class MainApplication: Application() {

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

}
public class MainApplication extends Application {

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

}
export default class App extends React.Component {
  
  componentDidMount() {
    Purchases.setDebugLogsEnabled(true);
    Purchases.setup("public_sdk_key", "my_app_user_id");
  }
  
}
document.addEventListener("deviceready", onDeviceReady, false);

function onDeviceReady() {
    Purchases.setDebugLogsEnabled(true);
    Purchases.setup(
      "public_sdk_key",
      "my_app_user_id"
    );
}
See Unity installation instructions https://docs.revenuecat.com/docs/unity
Future<void> initPlatformState() async {
  Purchases.setDebugLogsEnabled(true);
  await Purchases.setup("public_sdk_key", "my_app_user_id");
}

πŸ“˜

Usage without User IDs

If your app doesn't have user IDs, you can use .configure(withAPIKey:) or pass nil for the appUserID. We will generate a random User ID for you.

Displaying Available Products

Purchases will automatically fetch the latest active entitlements 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 an upsell screen.

func displayUpsellScreen() {
    Purchases.shared.entitlements { (entitlements, error) in
        let vc = UpsellController()
        vc.entitlements = entitlements
        presentViewController(vc, animated: true, completion: nil)
    }
}
[[RCPurchases sharedPurchases] entitlementsWithCompletionBlock:^(RCEntitlements *entitlements, NSError *error) {
  UpsellViewController *vc = [[UpsellViewController alloc] init];
  vc.entitlements = entitlements;
  [self presentViewController:vc animated:YES completion:nil];
}];
Purchases.sharedInstance.getEntitlementsWith(onError = { /* Optional error handling */ }) { entitlementMap ->  

}
Purchases.getSharedInstance().getEntitlements(new ReceiveEntitlementsListener() {
    @Override
    public void onReceived(@Nullable Map<String, Entitlement> entitlements) {
        showScreen(entitlements);
    }
  
      @Override
    public void onError(@NonNull PurchasesError error) {
        /* Optional error handling */ 
    }
});
try {
  const entitlements = await Purchases.getEntitlements();
  showUpsellScreen(entitlements);
} catch (e) {

}
func displayUpsellScreen() {
  Purchases.getEntitlements(
      entitlements => {

      },
      error => {

      }
  );
}
var purchases = GetComponent<Purchases>();
purchases.GetProducts(new []{ "onemonth_freetrial", "annual_freetrial" }, (products, error) =>
{
    if (error != null) {
        LogError(error);
    }
});
try {
  Map<String, Entitlement> entitlements = await Purchases.getEntitlements();
  showUpsellScreen(entitlements);
} on PlatformException catch(e) {
  
}

πŸ“˜

Since the SDK pre-fetches the available products, the completion block to get entitlements 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.

πŸ“˜

Active products are nil

If your active products are returning nil, make sure they're configured correctly in the Play Store or App Store Connect.

Make a Purchase

When it comes time to make a purchase, Purchases has a simple method, makePurchase. The code sample below shows the process of purchasing a product and confirming it unlocks the "my_entitlement_identifier" content.

Any type of product (subscription or non-subscription) can be purchased with this method. If the error is nil, then the transaction succeeded.

Purchases.shared.makePurchase(product, { (transaction, purchaserInfo, error, cancelled) in
    if purchaserInfo?.entitlements.all["your_entitlement_id"]?.isActive == true {
        // Unlock that great "pro" content
    }
})
[[RCPurchases sharedPurchases] makePurchase:product withCompletionBlock:^(SKPaymentTransaction *transaction, RCPurchaserInfo *purchaserInfo, NSError *error, BOOL cancelled) {
    if (purchaserInfo.entitlements.all[@"your_entitlement_id"].isActive) {
    // User is "premium"
    }
}];
Purchases.sharedInstance.makePurchaseWith(
  this,
  product,
  onError = { error, userCancelled -> /* No purchase */ },
  onSuccess = { product, purchaserInfo ->
    if (purchaserInfo.entitlements["my_entitlement_identifier"]?.isActive == true) {
    // Unlock that great "pro" content
  }
})
Purchases.getSharedInstance().makePurchase(
    this,
    product,
    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 {
  const purchaseMade = await Purchases.makePurchase("product_id");
  if (typeof purchaseMade.purchaserInfo.entitlements.active.my_entitlement_identifier !== "undefined") {
    // Unlock that great "pro" content
  }
} catch (e) {
  showError(e);
}

// To purchase Android In-app products, an optional third parameter needs to be provided
await Purchases.makePurchase("product_id", null, "inapp");
Purchases.makePurchase(
  "product_id", 
  ({ productIdentifier, purchaserInfo }) => {
    if (typeof purchaserInfo.entitlements.active.my_entitlement_identifier !== "undefined") {
      // Unlock that great "pro" content
    }
  },
  error => {
    // Error making purchase
  }
);

// To purchase Android In-app products, an optional last parameter needs to be provided

Purchases.makePurchase(
  "product_id", 
  ({ productIdentifier, purchaserInfo }) => {
    if (typeof purchaserInfo.entitlements.active.my_entitlement_identifier !== "undefined") {
      // Unlock that great "pro" content
    }
  },
  error => {
    // Error making purchase
  },
  null,
  "inapp"
);
Purchases purchases = GetComponent<Purchases>();
purchases.MakePurchase(product, (productIdentifier, purchaserInfo, userCancelled, error) =>
{
    foreach(string sub in info.ActiveSubscriptions)
    {
        if (item.Contains("my_product_identifier")) {
           // Unlock that great "pro" content
        }
    }
});
try {
  PurchaserInfo purchaserInfo = await Purchases.makePurchase(product.identifier);
  var isPro = purchaserInfo.entitlements.all["my_entitlement_identifier"].isActive;
  if (isPro) {
    // Unlock that great "pro" content
  }
} on PlatformException catch (e) {
  if (!(e.details as Map)["userCancelled"]) {
    showError(e);
  }
}

// To purchase Android In-app products, an optional type parameter needs to be provided
await Purchases.makePurchase(product.identifier, type: "inapp");

πŸ“˜

makePurchase 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 {
  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) =>
{
    foreach(string sub in info.ActiveSubscriptions)
    {
        if (item.Contains("my_product_identifier")) {
           // User has "pro" access
        }
    }
});
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
}

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 when 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 whether a 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 {
  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
});
try {
  PurchaserInfo restoredInfo = await Purchases.restoreTransactions();
  // ... check restored purchaserInfo to see if entitlement is now active
} on PlatformException catch (e) {
  // Error restoring purchases
}

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: 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(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
}
Purchases.addPurchaserInfoUpdateListener((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 year 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.