Checking Subscription Status

Determining the subscription status for a user.

RevenueCat makes it easy to determine subscription status and more with the Purchases SDK and REST API.

509

Get User Information

The CustomerInfo object contains all of the purchase and subscription data available about the user. 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():

// 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.getCustomerInfo({ error -> /* Optional error handling */ }) { 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
  }
);
try {
  const customerInfo = await Purchases.getCustomerInfo();
  // access latest customerInfo
} catch (error) {
  // Error fetching customer info
}
var purchases = GetComponent<Purchases>();
purchases.GetCustomerInfo((customerInfo, error) =>
{
  // access latest customerInfo
});

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.

📘

Working with the 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.

The CustomerInfo object gives you access to the following information about a user:

NameDescription
Request DateThe server date when the current CustomerInfo object was fetched. This is affected by the cache on device so you should not use it when you need the current time to calculate info such as time elapsed since purchase date. For that you should use device time.
Original App User IDThe original App User ID recorded for this user. May be the same as their current App User ID. See our Identifying Users guide for more information.
First SeenThe date this user was first seen in RevenueCat. This is the install date in most cases
Original Application VersioniOS only. The version number for the first version of the app this user downloaded. Will be nil unless a receipt has been recorded for the user through a purchase, restore, or import.

Note in sandbox this will always be "1.0"

Useful for migrating existing apps to subscriptions.
Original Purchase DateiOS only. The date that the app was first purchased/downloaded by the user. Will be nil if no receipt is recorded for the user. Useful for migrating existing apps to subscriptions.
Management URLURL to manage the active subscription of the user. If the user has an active iOS subscription, this will point to the App Store, if the user has an active Play Store subscription it will point there. For Stripe subscriptions, there is no Management URL.

If there are no active subscriptions it will be null.

If the user has multiple active subscriptions for different platforms, this will take the value of the OS in the X-Platform header into consideration:
- If the request was made on an OS for which there are active subscriptions, this will return the URL for the store that matches the header.
- If the request was made on a different OS or the OS was not included in the X-Platform header, this will return the URL for the store of the subscription with the farthest future expiration date.
All Purchased Product IdentifiersAn array of product identifiers purchased by the user regardless of expiration.
Non Consumable PurchasesAn array of all the non-consumable product identifiers purchased by the user.
Active SubscriptionsAn array of subscription product identifiers that are active. You should be using entitlement though.
EntitlementsEntitlementInfo objects that contain information about the user's entitlements, such as subscription state. See more below.

Get Entitlement Information

The EntitlementInfo object gives you access to all of the information about the status of a user's entitlements.

NameDescription
IdentifierThe entitlement identifier configured in the RevenueCat dashboard.
Product IdentifierThe underlying product identifier that unlocked this entitlement.
Is ActiveWhether or not the user has access to this entitlement.
Will RenewWhether or not the entitlement is set to renew at the end of the current period.

Note there may be a multiple hour delay between the value of this property and the actual state in the App Store / Play Store.
Period TypeThe period type this entitlement is in, can be one of:
- Trial: In a free trial period
- Promotional: In a promotional period
- Intro: In an introductory price period
- Normal: In the default period
Latest Purchase DateThe latest purchase or renewal date for this entitlement.
Original Purchase DateThe first date this entitlement was purchased. May be the same as the latest purchase date.
Expiration DateThe expiration date for the entitlement, can be null for lifetime access. If the period type is trial then this is the trial expiration date.
StoreThe store that unlocked this entitlement, can be one of:
- App Store
- Mac App Store
- Play Store
- Amazon Appstore
- Stripe
- Promotional (RevenueCat)
Is SandboxWhether this entitlement was unlocked from a sandbox or production purchase.
Unsubscribe Detected AtThe date an unsubscribe was detected. An unsubscribe does not mean that the entitlement is inactive.

Note there may be a multiple hour delay between the value of this property and the actual state in the App Store / Play Store. This delay can be reduced by enabling Platform Server Notifications.
Billing Issue Detected AtThe date a billing issue was detected, will be null again once billing issue resolved. A billing issue does not mean that the entitlement is inactive.

Note there may be a multiple hour delay between the value of this property and the actual state in the App Store / Play Store. This delay can be reduced by enabling Platform Server Notifications.

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.

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>).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 your app has multiple entitlements and you need to check if a user is subscribed to at least one you can also check for the entitlement Id in the active dictionary of EntitlementInfo objects.

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
}

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.

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.

// 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
  }   
}
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
    }
}

Web Apps

If you also have a web app, or need to get a user's subscription status from outside of the Purchases SDK, you should use the REST API. You can read the full API reference here.

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.

Next Steps