RevenueCat Error Codes (PurchasesErrorCode)
Overview
RevenueCat surfaces a typed error code on every failure. Instead of parsing free-form message strings, you switch on a named value and respond to each case correctly. The exact type depends on the platform:
- iOS (Swift): errors bridge to the
RevenueCat.ErrorCodeenum, for example.purchaseCancelledError,.networkError,.configurationError. - Android (Kotlin): a
PurchasesErrorexposes a.codeproperty of typePurchasesErrorCode, for examplePurchasesErrorCode.PurchaseCancelledError,NetworkError,ConfigurationError. - React Native: the caught error exposes a string
error.codefrom thePURCHASES_ERROR_CODEenum, for example'PURCHASE_CANCELLED_ERROR', plus a convenienterror.userCancelledboolean.
The single most important practical rule: always check the user cancelled flag first. When a user simply backs out of the purchase sheet, that is not a failure to surface. Return early and show nothing.
PurchasesErrorCode values. RevenueCat also returns a separate set of
backend error codes from its REST API. For the authoritative, always-current list of both, see the
official RevenueCat errors documentation.
Handle Errors in React Native
In react-native-purchases, purchase and configuration calls reject, so wrap them in
try/catch. The caught error object exposes error.code (a string from the
PURCHASES_ERROR_CODE enum), error.userCancelled (a boolean),
error.message, and error.underlyingErrorMessage:
import Purchases, { PURCHASES_ERROR_CODE } from 'react-native-purchases';
async function buy(pkg) {
try {
await Purchases.purchasePackage(pkg);
// Purchase succeeded.
} catch (e: any) {
if (e.userCancelled) return; // user backed out, not an error to surface
console.log(e.code, e.message); // e.code is a PURCHASES_ERROR_CODE string
switch (e.code) {
case PURCHASES_ERROR_CODE.NETWORK_ERROR:
// Transient: offer a retry.
break;
case PURCHASES_ERROR_CODE.PRODUCT_ALREADY_PURCHASED_ERROR:
// Already owned: try Purchases.restorePurchases().
break;
case PURCHASES_ERROR_CODE.CONFIGURATION_ERROR:
// Developer setup bug: fix product / API key configuration.
break;
default:
// Fall back to a friendly generic message.
// e.underlyingErrorMessage often has the store-level detail.
break;
}
}
}
PURCHASES_ERROR_CODE from
react-native-purchases and compare against its members rather than hardcoding the string
literals. This keeps your switch resilient to typos and easy to read.
Handle Errors in iOS
In Swift, RevenueCat errors bridge to the RevenueCat.ErrorCode enum. With the async API,
catch the thrown error and cast it so you can switch on the specific case:
import RevenueCat
func buy(_ pkg: Package) async {
do {
_ = try await Purchases.shared.purchase(package: pkg)
// Purchase succeeded.
} catch {
if let rcError = error as? RevenueCat.ErrorCode {
switch rcError {
case .purchaseCancelledError:
// User backed out: not an error to surface.
return
case .networkError:
// Transient: offer a retry.
break
case .configurationError:
// Developer setup bug: fix product / API key configuration.
break
default:
// Fall back to a friendly generic message.
break
}
}
}
}
.purchaseCancelledError case, so check for it first and return. If you are
unsure of the exact cast in your SDK version, keep it simple and consult the
RevenueCat errors docs.
Handle Errors in Android
On Android, the callback and coroutine APIs surface a PurchasesError with a
.code property of type PurchasesErrorCode. The purchaseWith
onError callback also hands you a separate userCancelled boolean, so you can
short-circuit before inspecting the code:
import com.revenuecat.purchases.Purchases
import com.revenuecat.purchases.PurchasesErrorCode
Purchases.sharedInstance.purchaseWith(
params,
onError = { error, userCancelled ->
if (userCancelled) return@purchaseWith // user backed out, not an error
when (error.code) {
PurchasesErrorCode.NetworkError -> {
// Transient: offer a retry.
}
PurchasesErrorCode.ProductAlreadyPurchasedError -> {
// Already owned: try restorePurchases().
}
PurchasesErrorCode.ConfigurationError -> {
// Developer setup bug: fix product / API key configuration.
}
else -> {
// Fall back to a friendly generic message.
}
}
},
onSuccess = { _, customerInfo ->
// Purchase succeeded.
}
)
Common Error Codes
These are real RevenueCat PurchasesErrorCode names. The casing shown matches the Android
enum; React Native uses the SCREAMING_SNAKE_CASE form (for example
PURCHASE_CANCELLED_ERROR) and iOS uses the lowerCamelCase case (for example
.purchaseCancelledError). The meaning is the same across platforms.
| PurchasesErrorCode | What it means | Typical fix |
|---|---|---|
PurchaseCancelledError |
The user dismissed the purchase sheet. | Not a real error. Check the cancelled flag first and return early. |
StoreProblemError |
A problem occurred with the App Store or Google Play (an outage or an unexpected store response). | Transient. Ask the user to try again later. Check store status pages. |
PurchaseNotAllowedError |
The device is not allowed to make purchases (for example parental controls or restrictions). | Tell the user to check device purchase restrictions and store account settings. |
PurchaseInvalidError |
The purchase was invalid, often due to a payment problem on the store account. | Ask the user to verify their payment method in their store account. |
ProductNotAvailableForPurchaseError |
The product is not available for purchase right now. | Confirm the product is approved and configured in the store and in RevenueCat. |
ProductAlreadyPurchasedError |
The user already owns this product (common with non-consumables and active subscriptions). | Call restorePurchases() to grant the existing entitlement. |
ReceiptAlreadyInUseError |
The receipt is already associated with a different RevenueCat app user ID. | Reconcile identity. Review your app user ID and login flow. |
InvalidReceiptError |
The store receipt could not be validated. | See the invalid receipt guide for sandbox and validation fixes. |
MissingReceiptFileError |
No receipt file was found on the device (common in iOS sandbox). | Have the user sign in to a sandbox account, then retry the purchase or restore. |
NetworkError |
The SDK could not reach RevenueCat or the store servers. | Transient. Retry with backoff. See the network error guide. |
InvalidCredentialsError |
The API key is missing, wrong, or for the wrong platform. | Developer bug. Use the correct platform-specific public API key in configure(). |
UnexpectedBackendResponseError |
RevenueCat returned a response the SDK could not parse. | Usually transient. Retry. If persistent, check status and update the SDK. |
InvalidAppUserIdError |
The app user ID is missing or malformed. | Pass a stable, valid app user ID to logIn() or configure(). |
OperationAlreadyInProgressError |
The same operation (for example a purchase) is already running. | Disable the buy button while a purchase is in flight. Do not call twice. |
ConfigurationError |
Something in the RevenueCat or store configuration is wrong (for example products not set up). | Developer bug. See the configuration error guide. |
UnknownError |
An unexpected error that does not map to a known case. | Log the underlying message, show a friendly generic message, and offer a retry. |
PurchasesErrorCode value instead of a
numeric index, since the names are stable and self-documenting across platforms and SDK versions.
User Cancelled Is Not a Real Error
When a user opens the purchase sheet and then taps Cancel, the SDK reports it through the error channel, but it is not a failure you should surface. Showing an error alert here is a common bug that makes a normal flow feel broken. Always branch on the cancellation signal first:
- React Native: check
error.userCancelled(boolean); the code isPURCHASE_CANCELLED_ERROR. - Android: the
purchaseWithonErrorcallback gives you auserCancelledboolean; the code isPurchasesErrorCode.PurchaseCancelledError. - iOS: the error bridges to the
.purchaseCancelledErrorcase ofRevenueCat.ErrorCode.
try {
await Purchases.purchasePackage(pkg);
} catch (e: any) {
if (e.userCancelled) return; // do NOT show an error: the user simply cancelled
// ...handle the real error by e.code
}
Related Error Guides
- RevenueCat Configuration Error —
ConfigurationError/InvalidCredentialsErrorsetup fixes - RevenueCat Network Error —
NetworkError, retries, and offline handling - RevenueCat Invalid Receipt —
InvalidReceiptErrorand receipt validation - Configure the RevenueCat SDK — apiKey and appUserID setup that prevents many errors
- Get Products and Prices — load offerings so products are available for purchase
- RevenueCat Docs: Errors — the authoritative list of SDK and backend error codes
FAQ
What is PurchasesErrorCode in RevenueCat?
It is the typed enumeration RevenueCat surfaces on every failure. iOS bridges to
RevenueCat.ErrorCode, Android exposes PurchasesErrorCode on a
PurchasesError.code, and React Native gives a string error.code from the
PURCHASES_ERROR_CODE enum. Switch on the code to respond to each case correctly.
How do I check if the user cancelled?
Check the cancellation flag first. React Native exposes error.userCancelled, Android passes a
userCancelled boolean to the onError callback, and iOS bridges to the
.purchaseCancelledError case. Return early and show nothing.
What does RevenueCat error 23 mean?
Match on the named PurchasesErrorCode value (for example ConfigurationError or
NetworkError) rather than a raw number. RevenueCat also has separate backend error codes
returned by the REST API. The official errors docs are the authoritative list.
How do I handle RevenueCat errors in code?
Use try/catch (React Native, Swift async) or the onError callback (Android),
check the cancellation flag first, then switch on the code. Treat network errors as transient with a
retry, treat configuration and credential errors as developer bugs to fix, and use a friendly generic
message for UnknownError.