Getting Subscription Status
RevenueCat makes it easy to determine subscription status and more with the RevenueCat SDK and REST API.
Getting subscription status via the SDK
The CustomerInfo object contains all of the purchase and subscription data available about a customer.
This object is updated whenever a purchase or restore occurs and periodically throughout the lifecycle of your app. The latest information can always be retrieved by calling getCustomerInfo()
:
- Swift
- Obj-C
- Kotlin
- Java
- Flutter
- React Native
- Cordova
- Capacitor
- Unity
- Web (JS/TS)
// Using Swift Concurrency
do {
let customerInfo = try await Purchases.shared.customerInfo()
} catch {
// handle error
}
// Using Completion Blocks
Purchases.shared.getCustomerInfo { (customerInfo, error) in
// access latest customerInfo
}
[[RCPurchases sharedPurchases] customerInfoWithCompletion:^(RCCustomerInfo * customerInfo, NSError * error) {
// access latest customerInfo
}];
Purchases.sharedInstance.getCustomerInfoWith(
onError = { error -> /* Optional error handling */ },
onSuccess = { customerInfo -> /* Access latest customerInfo */ },
)
Purchases.getSharedInstance().getCustomerInfo(new ReceiveCustomerInfoCallback() {
@Override
public void onReceived(@NonNull CustomerInfo customerInfo) {
// access latest customerInfo
}
@Override
public void onError(@NonNull PurchasesError error) {
}
});
try {
CustomerInfo customerInfo = await Purchases.getCustomerInfo();
// access latest customerInfo
} on PlatformException catch (e) {
// Error fetching customer info
}
try {
const customerInfo = await Purchases.getCustomerInfo();
// access latest customerInfo
} catch (e) {
// Error fetching customer info
}
Purchases.getCustomerInfo(
customerInfo => {
// access latest customerInfo
},
error => {
// Error fetching customer info
}
);
await Purchases.addCustomerInfoUpdateListener((customerInfo) => {
// handle any changes to customerInfo
});
var purchases = GetComponent<Purchases>();
purchases.GetCustomerInfo((customerInfo, error) =>
{
// access latest customerInfo
});
try {
customerInfo = await Purchases.getSharedInstance().getCustomerInfo();
// access latest customerInfo
} catch (e) {
// Handle errors fetching customer info
}
It's safe to call getCustomerInfo()
frequently throughout your app. Since the SDK updates and caches the latest CustomerInfo when the app becomes active, the completion block won't need to make a network request in most cases.
Checking If A User Is Subscribed
The subscription status for a user can easily be determined with the CustomerInfo
and EntitlementInfo
objects.
For most apps that only have one entitlement, the isActive
status can be quickly checked for your entitlement ID.
- Swift
- Obj-C
- Kotlin
- Java
- Flutter
- React Native
- Cordova
- Unity
- Web (JS/TS)
if customerInfo.entitlements[<your_entitlement_id>]?.isActive == true {
// user has access to "your_entitlement_id"
}
if (customerInfo.entitlements[@<your_entitlement_id>].isActive) {
// user has access to "your_entitlement_id"
}
if (customerInfo.entitlements[<your_entitlement_id>]?.isActive == true) {
// user has access to "your_entitlement_id"
}
if (customerInfo.getEntitlements().get(<your_entitlement_id>) != null
&& customerInfo.getEntitlements().get(<your_entitlement_id>).isActive()) {
// user has access to "your_entitlement_id"
}
if (customerInfo.entitlements.all[<my_entitlement_identifier>].isActive) {
// Grant user "pro" access
}
if(typeof customerInfo.entitlements.active[<my_entitlement_identifier>] !== "undefined") {
// Grant user "pro" access
}
if(typeof customerInfo.entitlements.active[<my_entitlement_identifier>] !== "undefined") {
// Grant user "pro" access
}
if (customerInfo.Entitlements.Active.ContainsKey(<my_entitlement_identifier>)) {
// Unlock that great "pro" content
}
if ("gold_entitlement" in customerInfo.entitlements.active) {
// Grant user access to the entitlement "gold_entitlement"
grantEntitlementAccess();
}
If your app has multiple entitlements, you might also want to check if the customer has any active entitlements:
- Swift
- Obj-C
- Kotlin
- Java
- Flutter
- React Native
- Cordova
- Unity
- Web (JS/TS)
if !customerInfo.entitlements.active.isEmpty {
// user has access to some entitlement
}
if ([customerInfo.entitlements.active count] > 0) {
//user has access to some entitlement
}
if (customerInfo.entitlements.active.isNotEmpty()) {
//user has access to some entitlement
}
if (!customerInfo.getEntitlements().getActive().isEmpty()) {
//user has access to some entitlement
}
if (customerInfo.entitlements.active.isNotEmpty) {
//user has access to some entitlement
}
if (Object.entries(customerInfo.entitlements.active).length) {
//user has access to some entitlement
}
if (Object.entries(customerInfo.entitlements.active).length) {
//user has access to some entitlement
}
if (customerInfo.Entitlements.Active.Count != 0) {
//user has access to some entitlement
}
if (Object.keys(customerInfo.entitlements.active).length > 0) {
// User has access to some entitlement, grant entitlement access
grantEntitlementAccess();
}
It's important to note that CustomerInfo will be empty if no purchases have been made and no transactions have been synced. This means that entitlements may not exist in CustomerInfo even if they have been set up in the RevenueCat dashboard.
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, Google, or Amazon).
It is recommended that all apps have some way for users to trigger the restorePurchases method, even if you require all customers to create accounts.
See our Restoring Purchases guide for more information.
Cache
The SDK caches the user's subscription information to reduce your app's reliance on the network.
Users who unlock entitlements will be able to access them even without an internet connection. The SDK will update the cache if it's older than 5 minutes, but only if you call getCustomerInfo()
, make a purchase, or restore purchases, so it's a good idea to call getCustomerInfo()
any time a user accesses premium content.
Listening For CustomerInfo Updates
Since Purchases SDK works seamlessly on any platform, a user's CustomerInfo may change from a variety of sources. You can respond to any changes in CustomerInfo by conforming to an optional delegate method, purchases:receivedUpdated:
. This will fire whenever we receive a change in CustomerInfo on the current device and you should expect it to be called at launch and throughout the life of the app.
CustomerInfo 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 customer information the next time your app is launched. Or throughout your app as you request new CustomerInfo
objects.
- Swift
- Obj-C
- Kotlin
- Kotlin Multiplatform
- Java
- Flutter
- React Native
- Cordova
- Capacitor
- Unity
// Option 1: using PurchasesDelegate:
Purchases.logLevel = .debug
Purchases.configure(withAPIKey: <public_sdk_key>)
Purchases.shared.delegate = self // make sure to set this after calling configure
extension AppDelegate: PurchasesDelegate {
func purchases(_ purchases: Purchases, receivedUpdated customerInfo: Purchases.CustomerInfo) {
// handle any changes to customerInfo
}
}
// Option 2: using Swift Concurrency:
for try await customerInfo in Purchases.shared.customerInfoStream {
// handle any changes to customerInfo
}
- (void)purchases:(nonnull RCPurchases *)purchases receivedUpdatedCustomerInfo:(nonnull RCCustomerInfo *)customerInfo {
// handle any changes to customerInfo
}
class UpsellActivity : AppCompatActivity(), UpdatedCustomerInfoListener {
override fun onReceived(customerInfo: CustomerInfo) {
// handle any changes to customerInfo
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Purchases.sharedInstance.updatedCustomerInfoListener = this
}
}
Purchases.sharedInstance.delegate = object : PurchasesDelegate {
override fun onCustomerInfoUpdated(customerInfo: CustomerInfo) {
// handle any changes to customerInfo
}
}
public class UpsellActivity extends AppCompatActivity implements UpdatedCustomerInfoListener {
@Override public void onReceived(CustomerInfo customerInfo) {
// handle any changes to customerInfo
}
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Purchases.getSharedInstance().setUpdatedCustomerInfoListener(this);
}
}
Purchases.addCustomerInfoUpdateListener((info) {
// handle any changes to customerInfo
});
Purchases.addCustomerInfoUpdateListener((info) => {
// handle any changes to customerInfo
});
window.addEventListener("onCustomerInfoUpdated", (info) => {
// handle any changes to customerInfo
});
await Purchases.addCustomerInfoUpdateListener((customerInfo) => {
// handle any changes to customerInfo
});
public class PurchasesListener : Purchases.UpdatedCustomerInfoListener
{
public override void CustomerInfoReceived(Purchases.CustomerInfo customerInfo)
{
// handle any changes to CustomerInfo
}
}
Reference
CustomerInfo Reference
EntitlementInfo Reference
Getting subscription status via the REST API
If you need to get a user's subscription status from outside of the Purchases SDK, for example, from your own backend, you should use the REST API. You can read the full API reference here.
- Code
curl --request GET \
--url https://api.revenuecat.com/v1/subscribers/app_user_id \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer PUBLIC_API_KEY'
Handling Refunds
RevenueCat can handle refunds across all platforms for both subscription and non-subscription products. As soon as RevenueCat detects a refund, the CustomerInfo will be updated to reflect the correct entitlement status - no action required on your part! If you have questions about refunds, take a look at our community article covering the topic.
Offline Entitlements
In the very uncommon case that RevenueCat servers don't respond as expected, the SDK is prepared to verify Apple/Google/Amazon's purchases on the device itself and grant entitlements temporarily. This allows your customers to have an almost seamless experience in this unlikely scenario, improving even more on our reliability. This happens automatically, so you don't need to do anything, the entitlements will appear in the CustomerInfo
.
Offline Entitlements is automatically enabled when our SDK attempts to reach our servers and they can't respond. It automatically is disabled when our servers respond successful http responses again.
In order to do this, the SDK caches the relationships between products and entitlements you have setup in your RevenueCat dashboard. Then, when it tries to post a purchase to RevenueCat's servers, and these respond with an error, we will use the purchases from the stores and these relationships to grant entitlements.
Some things to note:
- No information is lost when Offline Entitlements are active. All purchases are recorded and will be processed by our servers automatically once they are back online, with no action needed from you or your users.
- The data for these purchases won't appear in our RevenueCat graphs and webhooks until it's successfully pushed.
- Purchases won't be recognized cross-platform while using Offline Entitlements.
- Offline Entitlements only gets enabled if the user makes a purchases while RevenueCat's servers are down. Otherwise, it will use any existing cached information, which does consider cross-platform purchases.
- Offline Entitlements don't currently work for one-time purchases (consumables and non-consumables). If our SDK detects that the user has made one of these purchases, Offline Entitlements will not be enabled and an error will be returned instead.
- Offline Entitlements are disabled when your app is completing transactions.
You can check more of this feature in our blogpost: https://www.revenuecat.com/blog/engineering/introducing-offline-entitlements/
Next Steps
- Once you're ready to test your integration, you can follow our guides on testing and debugging