---
id: "getting-started/quickstart"
title: "SDK Quickstart"
description: "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."
permalink: "/docs/getting-started/quickstart"
slug: "quickstart"
version: "current"
original_source: "docs/getting-started/quickstart.mdx"
---

> **AI agents:** This is the Markdown version of a RevenueCat documentation page. For the complete documentation index, see [llms.txt](https://www.revenuecat.com/docs/llms.txt).

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.

:::tip Using Stripe?
You can provision RevenueCat from your terminal or AI coding agent with the [Stripe Projects quickstart](https://www.revenuecat.com/docs/getting-started/stripe-projects-quickstart) — Stripe creates your account, project, and API keys in one command.
:::

## 1. Set up your RevenueCat account

Before continuing with this guide, follow our [Setting up RevenueCat](https://www.revenuecat.com/docs/welcome/overview) guide to create a free account and set up your first project.

[Set up your account →](https://www.revenuecat.com/docs/welcome/overview)

## 2. Product Configuration

Before using the SDK, you need to configure your products, entitlements, and offerings in the RevenueCat dashboard. Every new project comes with a **Test Store** automatically configured, so you can start immediately without connecting to Apple or Google.

[Configure Products →](https://www.revenuecat.com/docs/projects/configuring-products)

:::info\[Already configured your products?]
Continue to SDK installation below.
:::

:::tip\[SDK Version Requirements]
Test Store requires minimum SDK versions (iOS 5.43.0+, Android 9.9.0+, etc.). See [Sandbox Testing](https://www.revenuecat.com/docs/test-and-launch/sandbox#testing-with-revenuecat-test-store) for complete version requirements.
:::

## 3. Using RevenueCat's Purchases SDK

Our SDK seamlessly implements purchases and subscriptions across platforms while syncing tokens with the RevenueCat server.

### SDK Installation

Install the SDK on your preferred platform.

**Note: if you are using RevenueCat's Paywalls, the `RevenueCatUI` package is required during the installation process.**

If you run into issues with the SDK, see [Troubleshooting the SDKs](https://www.revenuecat.com/docs/test-and-launch/debugging/troubleshooting-the-sdks) for guidance.

[SDK Installation Guides →](https://www.revenuecat.com/docs/getting-started/installation)

### Initialize and Configure the SDK

:::info\[Only use your public API key to configure Purchases]
You can get your public API key from the **API keys** section 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](https://www.revenuecat.com/docs/getting-started/configuring-sdk) for more information and best practices.

Make sure you configure *Purchases* with your public API key only. You can find this API key in your app settings by selecting your app in the API keys page. You can read more about the different API keys available in our [Authentication guide](https://www.revenuecat.com/docs/projects/authentication).

```swift
// 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>)

}
```

```objectivec
// 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;
}
```

```kotlin
// 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())
        }
    }
}
```

```kotlin
import com.revenuecat.purchases.kmp.LogLevel
import com.revenuecat.purchases.kmp.Purchases
import com.revenuecat.purchases.kmp.configure

// If you have common initialization logic, call configure() there. If not,
// call it early in the app's lifecycle on the respective platforms.
// Note: make sure you use the correct api key for each platform. You could
// use Kotlin Multiplatform's expect/actual mechanism for this. 

Purchases.logLevel = LogLevel.DEBUG
Purchases.configure(apiKey = "<google_or_apple_api_key>") {
    appUserId = "<app_user_id>"
    // Other configuration options.
}
```

```java
// 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());
    }
}
```

```dart
import 'dart:io' show Platform;

//...

Future<void> initPlatformState() async {
  await Purchases.setLogLevel(LogLevel.debug);

  late 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);
}
```

```jsx
import { Platform } from 'react-native';
import { useEffect } from 'react';
import Purchases, { LOG_LEVEL } from 'react-native-purchases';
import { GALAXY_BILLING_MODE } from 'react-native-purchases-store-galaxy';

//...

export default function App() {

  useEffect(() => {
    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 });

      // OR: if building for Galaxy Store, install react-native-purchases-store-galaxy, then:
      Purchases.configure({
        apiKey: <revenuecat_project_galaxy_api_key>,
        store: 'GALAXY',
        galaxyBillingMode: GALAXY_BILLING_MODE.TEST,
      });
    }

  }, []);
}
```

```jsx
const onDeviceReady = async () => {
  await Purchases.setLogLevel({level: 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 });
}
```

```jsx
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});
  }
}
```

```cpp
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](https://www.revenuecat.com/docs/customers/user-ids) guide.

When in development, we recommend enabling more verbose debug logs. For more information about these logs, see our [Debugging](https://www.revenuecat.com/docs/test-and-launch/debugging) guide.

If you're planning to use RevenueCat alongside your existing purchase code, be sure to tell the SDK that [your app will complete the purchases](https://www.revenuecat.com/docs/migrating-to-revenuecat/sdk-or-not/finishing-transactions)

:::info\[Configuring Purchases with User IDs]
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](https://www.revenuecat.com/docs/customers/user-ids).
:::

### Present a Paywall

The SDK will automatically fetch your configured Offerings and retrieve product information from your Test Store or connected stores (Apple, Google, Amazon). You can present a paywall using RevenueCat's pre-built UI or build your own.

**Option A: Use RevenueCat Paywalls** (Recommended)

RevenueCat provides customizable paywall templates that you can configure remotely. See [Creating Paywalls](https://www.revenuecat.com/docs/tools/paywalls/creating-paywalls) to design your paywall, then [Displaying Paywalls](https://www.revenuecat.com/docs/tools/paywalls/displaying-paywalls) for platform-specific implementation.

[Present a Paywall →](https://www.revenuecat.com/docs/tools/paywalls/displaying-paywalls)

**Option B: Build Your Own**

If you prefer full control, manually display products and handle purchases. See [Displaying Products](https://www.revenuecat.com/docs/getting-started/displaying-products) and [Making Purchases](https://www.revenuecat.com/docs/getting-started/making-purchases).

#### SDK not fetching products or offerings?

A common issue when displaying your paywall or making a purchase is missing or empty offerings. This is almost always a configuration issue.

If you're running into this error, please see our [community post](https://community.revenuecat.com/sdks-51/why-are-offerings-or-products-empty-124) for troubleshooting steps.

### Make a Purchase

Once your paywall is presented, select one of your products to make a purchase. The SDK will handle the purchase flow automatically and send the purchase information to RevenueCat.

**Testing with Test Store:**

- Test Store purchases work immediately without any additional setup
- They behave just like real subscriptions in your app
- No real money is charged
- Perfect for development and testing your integration

**Testing with real stores:**

- The RevenueCat SDK automatically handles sandbox vs. production environments
- Each platform requires slightly different configuration steps to test in sandbox
- See [Sandbox Testing](https://www.revenuecat.com/docs/test-and-launch/sandbox) for platform-specific instructions

When the purchase is complete, you can find the purchase associated to the customer in the [RevenueCat dashboard](https://www.revenuecat.com/docs/dashboard-and-metrics/customer-profile). You can [search for the customer](https://www.revenuecat.com/docs/dashboard-and-metrics/customer-lists#find-an-individual-customer) by their App User ID that you configured, or by the automatically assigned `$RCAnonymousID` that you'll find in your logs.

**Note:** RevenueCat ***always*** validates transactions. Test Store purchases are validated by RevenueCat; real store purchases are validated by the respective platform (Apple, Google, etc.).

Additionally, the SDK will automatically update the customer's `CustomerInfo` object with the new purchase information. This object contains all the information about the customer's purchases and subscriptions.

*Want to manually call the `purchase` method? See [Making Purchases](https://www.revenuecat.com/docs/getting-started/making-purchases).*

### Check Subscription Status

The SDK makes it easy to check what active subscriptions the current customer has, too. This can be done by checking a user's `CustomerInfo` object to see if a specific Entitlement is active, or by checking if the active Entitlements array contains a specific Entitlement ID.

If you're not using Entitlements (you probably should be!) you can check the array of active subscriptions to see what product IDs from the respective store it contains.

```swift
// Using Swift Concurrency
let customerInfo = try await Purchases.shared.customerInfo()
if customerInfo.entitlements.all[<your_entitlement_id>]?.isActive == true {
    // User is "premium"
}
// Using Completion Blocks
Purchases.shared.getCustomerInfo { (customerInfo, error) in
    if customerInfo?.entitlements.all[<your_entitlement_id>]?.isActive == true {
        // User is "premium"
    }
}
```

```objectivec
[[RCPurchases sharedPurchases] getCustomerInfoWithCompletion:^(RCPurchaserInfo * customerInfo, NSError * error) {
    if (customerInfo.entitlements.all[@<your_entitlement_id>].isActive) {
    // User is "premium"
	}
}];
```

```kotlin
Purchases.sharedInstance.getCustomerInfoWith(
    onError = { error -> /* Optional error handling */ },
    onSuccess = { customerInfo ->
        if (customerInfo.entitlements[<my_entitlement_identifier>]?.isActive == true) {
            // Grant user "pro" access
        }
    }
)
```

```kotlin
Purchases.sharedInstance.getCustomerInfo(
  onError = { error -> /* Optional error handling */ },
  onSuccess = { customerInfo ->
    if (customerInfo.entitlements[<my_entitlement_identifier>]?.isActive == true) {
      // Grant user "pro" access
    }
  }
)
```

```java
Purchases.getSharedInstance().getCustomerInfo(new ReceiveCustomerInfoCallback() {
    @Override
    public void onReceived(@NonNull CustomerInfo customerInfo) {
        if (customerInfo.getEntitlements().get(<my_entitlement_identifier>).isActive()) {
            // Grant user "pro" access
        }
    }

    @Override
    public void onError(@NonNull PurchasesError purchasesError) {
    }
});
```

```dart
try {
  CustomerInfo customerInfo = await Purchases.getCustomerInfo();
  if (customerInfo.entitlements.all[<my_entitlement_identifier>].isActive) {
    // Grant user "pro" access
  }
} on PlatformException catch (e) {
  // Error fetching purchaser info
}
```

```jsx
try {
  const customerInfo = await Purchases.getCustomerInfo();
  if(typeof customerInfo.entitlements.active[<my_entitlement_identifier>] !== "undefined") {
    // Grant user "pro" access
  }
} catch (e) {
 // Error fetching purchaser info
}
```

```jsx
try {
  const customerInfo = await Purchases.getCustomerInfo();
  // access latest customerInfo
} catch (error) {
  // Error fetching customer info
}
```

```jsx
Purchases.getCustomerInfo(
  info => {
    const isPro = typeof customerInfo.entitlements.active[<my_entitlement_identifier>] !== "undefined";
  },
  error => {
    // Error fetching customer info
  }
);
```

```cpp
var purchases = GetComponent<Purchases>();
purchases.GetPurchaserInfo((info, error) =>
{
   if (purchaserInfo.Entitlements.Active.ContainsKey(<my_entitlement_identifier>)) {
    // Unlock that great "pro" content
  }
});
```

**Web**

```curl
curl --request GET \
  --url https://api.revenuecat.com/v1/subscribers/<app_user_id> \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer REVENUECAT_API_KEY' \
  --header 'Content-Type: application/json'
```

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 `CustomerInfo` 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.

:::info\[Here's a tip!]
You can access a lot more information about a subscription than simply whether it's active or not. See our guide on [Subscription Status](https://www.revenuecat.com/docs/customers/customer-info) to learn if subscription is set to renew, if there's an issue detected with the user's credit card, and more.
:::

### Reacting to Subscription Status Changes

You can respond to any changes in a customer's `CustomerInfo` by conforming to an optional delegate method, `purchases:receivedUpdated:`.

This method will fire whenever the SDK receives an updated `CustomerInfo` object from calls to `getCustomerInfo()`, `purchase(package:)`, `purchase(product:)`, or `restorePurchases()`.

**Note:** `CustomerInfo` updates are *not* pushed to your app from the RevenueCat backend, updates can only happen from an outbound network request to RevenueCat, as mentioned above.

Depending on your app, it may be sufficient to ignore the delegate and simply handle changes to customer information the next time your app is launched or in the completion blocks of the SDK methods.

```swift
// Additional configure setup
// 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_api_key>)
    Purchases.shared.delegate = self // make sure to set this after calling configure

    return true
}

extension AppDelegate: PurchasesDelegate {
    func purchases(_ purchases: Purchases, receivedUpdated customerInfo: CustomerInfo) {
        /// - handle any changes to the user's CustomerInfo
    }
}
```

```objectivec
// Additional configure setup
// 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_api_key>];
    RCPurchases.sharedPurchases.delegate = self;

    return YES;
}

- (void)purchases:(nonnull RCPurchases *)purchases receivedUpdatedCustomerInfo:(nonnull RCCustomerInfo *)customerInfo {
    // handle any changes to purchaserInfo
}
```

```kotlin
class UpsellActivity : AppCompatActivity(), UpdatedCustomerInfoListener {
		override fun onReceived(customerInfo: CustomerInfo) {
        // handle any changes to purchaserInfo
    }
}
```

```kotlin
Purchases.sharedInstance.delegate = object : PurchasesDelegate {
    override fun onCustomerInfoUpdated(customerInfo: CustomerInfo) {
        // handle any changes to customerInfo
    }
}
```

```java
public class UpsellActivity extends AppCompatActivity implements UpdatedCustomerInfoListener {
		@Override
    public void onReceived(CustomerInfo customerInfo) {
        // handle any changes to customerInfo
    }
}
```

```dart
Purchases.addCustomerInfoUpdateListener((customerInfo) => {
  // handle any changes to customerInfo
});
```

```jsx
Purchases.addCustomerInfoUpdateListener(info => {
	// handle any changes to purchaserInfo
});
```

```jsx
await Purchases.addCustomerInfoUpdateListener((customerInfo) => {
  // handle any changes to customerInfo
});
```

```jsx
// subscribe to the window event onCustomerInfoUpdated to get any changes that happen in the customerInfo
window.addEventListener("onCustomerInfoUpdated", onCustomerInfoUpdated, false);

//...

onCustomerInfoUpdated: function(info) {
    // handle any changes to customerInfo
}
```

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

### Restore Purchases

RevenueCat enables your users to restore their in-app purchases, reactivating any content that they previously purchased from the **same store account** (Apple, Google, or Amazon account). We recommend that all apps have some way for users to trigger the restore method. Note that Apple does require a restore mechanism in the event a user loses access to their purchases (e.g: uninstalling/reinstalling the app, losing their account information, etc).

By default, RevenueCat Paywalls include a 'Restore Purchases' button. You can also trigger this method programmatically.

```swift
Purchases.shared.restorePurchases { customerInfo, error in
    // ... check customerInfo to see if entitlement is now active
}
```

```objectivec
[[RCPurchases sharedPurchases] restorePurchasesWithCompletion:^(RCCustomerInfo *customerInfo, NSError *error) {
    //... check customerInfo to see if entitlement is now active
}];
```

```kotlin
Purchases.sharedInstance.restorePurchasesWith() { customerInfo ->
	//... check customerInfo to see if entitlement is now active
}
```

```kotlin
Purchases.sharedInstance.restorePurchases(
    onError = { error -> 
        // An error occurred.
    },
    onSuccess = { customerInfo ->
        //... check customerInfo to see if entitlement is now active
    }
)
```

```java
Purchases.getSharedInstance().restorePurchases(new ReceiveCustomerInfoCallback() {
	@Override
	public void onError(@NonNull PurchasesError error) {
    
  }

	@Override
	public void onReceived(@NonNull CustomerInfo customerInfo) {
    
  }
});
```

```dart
try {
  CustomerInfo customerInfo = await Purchases.restorePurchases();
  // ... check restored purchaserInfo to see if entitlement is now active
} on PlatformException catch (e) {
  // Error restoring purchases
}
```

```jsx
try {
  const restore = await Purchases.restorePurchases();
  // ... check restored purchaserInfo to see if entitlement is now active
} catch (e) {

}
```

```jsx
try {
  const customerInfo = await Purchases.restorePurchases();
  //... check customerInfo to see if entitlement is now active
} catch (error) {
  // Error restoring purchases
}
```

```jsx
Purchases.restorePurchases(
  info => {
    //... check customerInfo to see if entitlement is now active
  },
  error => {
    // Error restoring purchases
  }
);
```

```cpp
var purchases = GetComponent<Purchases>();
purchases.RestorePurchases((info, error) =>
{
    //... check purchaserInfo to see if entitlement is now active
});
```

If two different [App User IDs](https://www.revenuecat.com/docs/customers/user-ids) restore transactions from the same underlying store account (Apple, Google, or Amazon account) RevenueCat may attempt to create an alias between the two App User IDs and count them as the same user going forward. See our guide on [Restoring Purchases](https://www.revenuecat.com/docs/getting-started/restoring-purchases) for more information on the different configurable restore behaviors.

:::success\[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 complete examples of integrating the SDK, head over to our sample app resources.

[View Sample Apps →](https://www.revenuecat.com/docs/platform-resources/sample-apps)

## Next Steps

- If you want to use your own user identifiers, read about [setting app user IDs](https://www.revenuecat.com/docs/customers/user-ids).
- Once you're ready to test your integration, follow our guides on [testing and debugging](https://www.revenuecat.com/docs/test-and-launch/debugging).
- If you're moving to RevenueCat from another system, see our [migration guide](https://www.revenuecat.com/docs/migrating-to-revenuecat/migrating-existing-subscriptions).
- If you qualify for the App Store Small Business Program, check out our [guide on how to apply](https://www.revenuecat.com/docs/platform-resources/apple-platform-resources/app-store-small-business-program).
