SDK Quickstart
This guide will walk you through how to get up and running with subscriptions and RevenueCat's SDK with only a few lines of code.
1. Create a RevenueCat Account
Sign up for a new RevenueCat account here.
Organizations / Enterprise
We recommend using a company account when registering for RevenueCat and setting up your app within a project. You'll be able to invite the rest of your team as collaborators to your project, but only the project owner can manage billing. Project collaborators can't manage billing details.
2. Project and App Configuration
▶️ Create a Project
Navigate to the RevenueCat dashboard and add a new project from the dropdown in the top navigation menu called Projects.
▶️ Add an App / Platform
From Project Settings > Apps in the left menu of the project dashboard, select the platform for the app you will be adding.
The field App name is required to add your app to RevenueCat. For iOS apps In-App Purchase Key(iOS) is also required. To make test and production purchases, the Bundle ID (iOS) / Package Name (Android) as well as the Shared Secret(iOS) / Service Credentials (Android) must be configured.
If you try making purchases without a bundle ID and valid credentials, the SDK will fail with a 'receipt is not valid' error as RevenueCat will be unable to validate the transaction with the store.
The rest of the configuration fields can be added later.
After registering your app, we recommend setting up Platform Server Notifications. These notifications aren't required but will speed up webhooks and integration delivery times and reduce lag time updating your subscribers.
RevenueCat itself does not have separate environments for staging and production. Rather, the underlying transactions for users are differentiated by sandbox and production.
Any RevenueCat app can make both sandbox and production purchases from the stores. If you do have separate apps for staging and production, you can create multiple projects in RevenueCat to mirror your setup.
Furthermore, users are not separated by environment, either. The same user can have active sandbox purchases and active production purchases at the same time.
▶️ Service Credentials
Service credentials need to be set up for RevenueCat to communicate with the app stores on your behalf. See our guides App Store Connect Shared Secret, App Store In-App Purchase Key, Play Service Credentials, and Amazon Appstore Shared Secret for more information.
Note that Play service credentials can take up to 36 hours to propagate throughout Google's servers.
3. Product Configuration
▶️ Store Setup
Before you can start using RevenueCat to fetch products, you must configure your products in the respective stores. See the following guides for App Store Connect, Google Play Console, Amazon Appstore, and Stripe for help navigating through this process.
If you are selling iOS products, be sure to sign your 'Paid Applications Agreement' and fill out your bank and tax information in App Store Connect > Agreements, Tax, and Banking. This needs to be completed before you can test any purchases.
On iOS, you can delay configuring products in App Store Connect by testing with StoreKit Configuration files instead. These config files require minimal setup and are configurable via Xcode directly.
Read more about setting up StoreKit Configuration files in our Sandbox Testing guide.
▶️ Configure Products and Entitlements in RevenueCat
Once your in-app products have been configured in App Store Connect, Google Play Console, Amazon Appstore, or Stripe, you'll need to copy that configuration into the RevenueCat dashboard. RevenueCat uses an Entitlements system to control access to premium features, and Offerings to manage the set of products you offer to customers.
Entitlements are the level of access that a customer is "entitled" to after purchasing a specific product, and Offerings is a simple way for you to organize the in-app products you wish to "offer" on your paywall and configure them remotely. We recommend utilizing these features to simplify your code and enable you to change products without releasing an app update.
See Configuring Products to set up your products and then organize them into Offerings or Entitlements.
4. Using RevenueCat's Purchases SDK
Our SDK seamlessly implements purchases and subscriptions across platforms while syncing tokens with the RevenueCat server.
▶️ Installation
Install the SDK on your preferred platform with our installation guides below.
- iOS/watchOS/tvOS Installation
- Android Installation
- React Native Installation
- Flutter Installation
- Capacitor / Ionic Installation
- Cordova Installation
- Unity Installation
- macOS / Catalyst Installation
If you run into issues with the SDK, see Troubleshooting the SDKs for guidance.
▶️ Initialize and Configure the SDK
You can get your public SDK key from the API keys tab under Project settings in the dashboard.
You should only configure the shared instance of Purchases once, usually on app launch. After that, 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. This API key can be found in the API Keys Project settings page. You can read more about the different API keys available in our Authentication guide.
- Swift
- Code
- Kotlin
- Java
- Flutter
- React Native
- Capacitor
- Cordova
- Unity
// on iOS and tvOS, use `application:didFinishLaunchingWithOptions:`
// on macOS and watchOS use `applicationDidFinishLaunching:`
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Purchases.logLevel = .debug
Purchases.configure(withAPIKey: <revenuecat_project_apple_api_key>, appUserID: <app_user_id>)
}
// on iOS and tvOS, use `application:didFinishLaunchingWithOptions:`
// on macOS and watchOS use `applicationDidFinishLaunching:`
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
RCPurchases.logLevel = RCLogLevelDebug;
[RCPurchases configureWithAPIKey:@<revenuecat_project_apple_api_key> appUserID:<app_user_id>];
return YES;
}
// If you're targeting only Google Play Store
class MainApplication: Application() {
override fun onCreate() {
super.onCreate()
Purchases.logLevel = LogLevel.DEBUG
Purchases.configure(PurchasesConfiguration.Builder(this, <revenuecat_project_google_api_key>).build())
}
}
// If you're building for the Amazon Appstore, you can use flavors to determine which keys to use
// In your build.gradle:
flavorDimensions "store"
productFlavors {
amazon {
buildConfigField "String", "STORE", "\"amazon\""
}
google {
buildConfigField "String", "STORE", "\"google\""
}
}
///...
class MainApplication: Application() {
override fun onCreate() {
super.onCreate()
Purchases.logLevel = LogLevel.DEBUG
if (BuildConfig.STORE.equals("amazon")) {
Purchases.configure(AmazonConfiguration.Builder(this, <revenuecat_project_amazon_api_key>).build())
} else if (BuildConfig.STORE.equals("google")) {
Purchases.configure(PurchasesConfiguration.Builder(this, <revenuecat_project_google_api_key>).build())
}
}
}
// If you're targeting only Google Play Store
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Purchases.setLogLevel(LogLevel.DEBUG);
Purchases.configure(new PurchasesConfiguration.Builder(this, <revenuecat_project_google_api_key>).build());
}
}
// If you're building for the Amazon Appstore,
// click the Kotlin tab to see how to set up flavors in your build.gradle:
///...
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Purchases.setLogLevel(LogLevel.DEBUG);
PurchasesConfiguration.Builder builder = null;
if (BuildConfig.STORE.equals("amazon")) {
builder = new AmazonConfiguration.Builder(this, <revenuecat_project_amazon_api_key>);
} else if (BuildConfig.STORE.equals("google")) {
builder = new PurchasesConfiguration.Builder(this, <revenuecat_project_google_api_key>);
}
Purchases.configure(builder.build());
}
}
import 'dart:io' show Platform;
//...
Future<void> initPlatformState() async {
await Purchases.setDebugLogsEnabled(true);
PurchasesConfiguration configuration;
if (Platform.isAndroid) {
configuration = PurchasesConfiguration(<revenuecat_project_google_api_key>);
if (buildingForAmazon) {
// use your preferred way to determine if this build is for Amazon store
// checkout our MagicWeather sample for a suggestion
configuration = AmazonConfiguration(<revenuecat_project_amazon_api_key>);
}
} else if (Platform.isIOS) {
configuration = PurchasesConfiguration(<revenuecat_project_apple_api_key>);
}
await Purchases.configure(configuration);
}
import { Platform } from 'react-native';
//...
export default class App extends React.Component {
componentDidMount() {
Purchases.setLogLevel(LOG_LEVEL.VERBOSE);
if (Platform.OS === 'ios') {
Purchases.configure({apiKey: <revenuecat_project_apple_api_key>});
} else if (Platform.OS === 'android') {
Purchases.configure({apiKey: <revenuecat_project_google_api_key>});
// OR: if building for Amazon, be sure to follow the installation instructions then:
Purchases.configure({ apiKey: <revenuecat_project_amazon_api_key>, useAmazon: true });
}
}
}
const onDeviceReady = async () => {
await Purchases.setLogLevel(LOG_LEVEL.DEBUG);
if (Capacitor.getPlatform() === 'ios') {
await Purchases.configure({ apiKey: <public_apple_api_key> });
} else if (Capacitor.getPlatform() === 'android') {
await Purchases.configure({ apiKey: <public_google_api_key> });
}
// OR: if building for Amazon, be sure to follow the installation instructions then:
await Purchases.configure({ apiKey: <public_amazon_api_key>, useAmazon: true });
}
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady() {
Purchases.setLogLevel(LOG_LEVEL.DEBUG);
if (window.cordova.platformId === 'ios') {
Purchases.configure(<revenuecat_project_apple_api_key>);
} else if (window.cordova.platformId === 'android') {
Purchases.configure(<revenuecat_project_google_api_key>);
// OR: if building for Amazon, be sure to follow the installation instructions then:
await Purchases.configure({apiKey: <revenuecat_project_amazon_api_key>, useAmazon: true});
}
}
See Unity installation instructions https://docs.revenuecat.com/docs/unity
The app_user_id
field in .configure
is how RevenueCat identifies users of your app. You can provide a custom value here or omit it for us to generate an anonymous id. For more information, see our Identifying Users guide.
When in development, we recommend enabling more verbose debug logs. For more information about these logs, see our Debugging guide.
If you're planning to use RevenueCat alongside your existing purchase code, check out our guide on Observer Mode.
If you have a user authentication system in your app, you can provide a user identifier at the time of configuration or at a later date with a call to .logIn()
. To learn more, check out our guide on Identifying Users.
▶️ Fetch and Display Available Products
The SDK will automatically fetch the configured Offerings and retrieve the product information from Apple, Google, or Amazon. Thus, available products will already be loaded when customers launch your purchase screen.
Below is an example of fetching Offerings. You can utilize Offerings to organize your paywall screen. See our guide on Displaying Products for more information and best practices.
- Swift
- Objective-C
- Kotlin
- Java
- Flutter
- React Native
- Capacitor
- Cordova
- Unity
- Web
// Using Swift Concurrency
do {
let offerings = try await Purchases.shared.offerings()
// Display current offering with offerings.current
} catch let error {
// handle error
}
// Using Completion Blocks
Purchases.shared.getOfferings { (offerings, error) in
if let offerings {
// Display current offering with offerings.current
}
}
[[RCPurchases sharedPurchases] getOfferingsWithCompletion:^(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 ReceiveOfferingsCallback() {
@Override
public void onReceived(@NonNull Offerings offerings) {
}
@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() {
try {
const offerings = await Purchases.getOfferings();
if (offerings.current !== null) {
// Display current offering with offerings.current
}
} catch (error) {
// Handle error
}
}
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
}
});
curl --request GET \
--url https://api.revenuecat.com/v1/subscribers/<app_user_id>/offerings \
--header 'Accept: application/json' \
--header 'Authorization: Bearer REVENUECAT_API_KEY' \
--header 'Content-Type: application/json' \
--header 'X-Platform: stripe'
See it in action
Swift
Kotlin
Flutter
React Native
If fetching your Offerings, products, or available packages are empty, it's due to some configuration issue in the respective store.
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. In Google Play 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 troubleshooting this issue in our Help Center.