This is the final article in our series on implementing in-app purchases for iOS. Follow the links below to catch up on the previous ones:
- iOS in-app purchases, part 1: App Store Connect and project configuration
- iOS in-app purchases, part 2: purchase initialization and processing
- iOS in-app purchases, part 3: testing purchases in Xcode
- iOS in-app purchases, part 4: server-side receipt validation
- iOS in-app purchases: the list of SKError codes and how to handle them
In this post, I will address the various kinds of SKErrors you might encounter: what problems they reveal and what to do about them. For each error, we will work out three aspects: why it occurs, how to handle them, and what message to display in the error notification.
SKError quick reference table
Use this table for quick lookup of error codes. For detailed explanations, see the sections below.
| Code | Name | Cause | Developer action |
| 0 | unknown | Unknown error occurred | Retry, check sandbox account |
| 1 | clientInvalid | User cannot make purchase | None required |
| 2 | paymentCancelled | User cancelled payment | Don’t show error; consider win-back |
| 3 | paymentInvalid | Payment method declined | None required |
| 4 | paymentNotAllowed | Device cannot authorize payments | None required |
| 5 | storeProductNotAvailable | Product unavailable in region | Check Storefront API |
| 6-9 | Cloud service errors | Apple Music / cloud related | None required (rare) |
| 10 | unauthorizedRequestData | Missing IAP entitlement | Complete IAP setup in Xcode |
| 11-14 | Offer errors | Invalid offer config/signature | Fix offer in App Store Connect |
| 15-17 | Overlay errors | Purchase UI issues | Check UI presentation code |
| 18 | ineligibleForOffer | User doesn’t qualify for offer | Show alternative offer |
| 19-20 | Platform/scene errors | Unsupported platform or wrong scene | Check platform requirements |
What is SKErrorDomain?
SKErrorDomain is the error domain for StoreKit 1 errors in iOS. When an in-app purchase fails, the system returns an NSError with domain “SKErrorDomain” and a code from 0-20 indicating the specific failure reason. Each code corresponds to an SKError.Code enumeration value defined in Apple’s StoreKit framework.
For each error in this guide, we cover three aspects: why it occurs, how to handle it, and what message to display to the user.

StoreKit 1 error codes (SKError)
SKError.Code.unknown (Code 0)
What it means: An error occurred for an unknown reason. This is a catch-all for unspecified errors.
How to fix it: No direct action is usually needed. However, this error commonly occurs when testing in iOS 14+ simulators. Use StoreKit Configuration file for local testing or test on a real device. Also check if sandbox accounts are properly configured and App Store Connect agreements are accepted.
User message: “The purchase is unavailable for an unknown reason. Please try again later.”
SKError.Code.clientInvalid (Code 1)
What it means: The user cannot make a purchase. This typically happens when a child tries to buy something with parental controls enabled or when the Apple ID is restricted.
How to fix it: No action is needed from the developer’s side. This is a user account restriction.
User message: “The purchase cannot be completed with this account.”
SKError.Code.paymentCancelled (Code 2)
What it means: The user got to the payment screen but changed their mind and clicked “cancel”. This is one of the most common “errors” you’ll encounter.
How to fix it: From a technical point of view, no error occurred and nothing needs to be done. From a marketing perspective, you could implement win-back strategies such as offering a discount or showing alternative pricing options.
Important: This error can also occur due to network timeouts during confirmation, Strong Customer Authentication (SCA) failures in the EU, or Ask to Buy rejection by a parent. Don’t assume it’s always intentional cancellation.
User message: Best practice is to show nothing. Optionally: “You have canceled your purchase. Would you like to try a subscription at a discounted price?”
SKError.Code.paymentInvalid (Code 3)
What it means: The payment failed because the card was expired, there were insufficient funds, or another payment method issue occurred.
How to fix it: No action is needed from the developer’s side.
User message: “Your purchase was declined. Please check your payment method and update if necessary.”
SKError.Code.paymentNotAllowed (Code 4)
What it means: The device is not allowed to authorize payments. This could be due to parental controls, enterprise/corporate policies, or the device/user not being authorized to make purchases.
How to fix it: No action is needed from the developer’s side.
User message: “Purchases are disabled on this device. Please check your settings or parental controls.”
SKError.Code.storeProductNotAvailable (Code 5)
What it means: The user is trying to buy a product that is not available in their App Store region.
How to fix it: Check product availability using the Storefront API before displaying products. Filter out unavailable products from your UI.
User message: “This product is not available in your region. Please check your App Store region and try again.”
Cloud service errors (Codes 6-9)
These errors relate to cloud services like Apple Music:
- Code 6 (cloudServicePermissionDenied): User has not allowed access to cloud service information
- Code 7 (cloudServiceNetworkConnectionFailed): No internet connection during purchase
- Code 8 (cloudServiceRevoked): Device doesn’t have access to StoreKit payment service (common in Sandbox)
- Code 9 (privacyAcknowledgementRequired): User hasn’t acknowledged Apple’s privacy policy for Apple Music
SKError.Code.unauthorizedRequestData (Code 10)
What it means: Your app ID doesn’t contain the required entitlement for using StoreKit.
How to fix it: Complete the IAP setup process: add the In-App Purchase capability in Xcode, ensure your bundle ID matches App Store Connect, and verify all agreements are accepted.
User message: “An error has occurred. Please try again later.”
Promotional offer errors (Codes 11-14)
These errors relate to subscription promotional offers:
- Code 11 (invalidOfferIdentifier): Offer ID not set up in App Store Connect or revoked. Fix: verify offer exists and is active in ASC.
- Code 12 (invalidSignature): Signature for promotional offer is incorrect. Fix: regenerate using correct subscription key (.p8 file). If using Adapty, verify your Subscription Key is properly configured.
- Code 13 (missingOfferParams): SKPaymentDiscount parameters are missing. Fix: ensure all required parameters are provided.
- Code 14 (invalidOfferPrice): Discounted price is higher than base price. Fix: update pricing in App Store Connect.
Overlay and platform errors (Codes 15-20)
These errors relate to purchase UI presentation:
- Code 15 (overlayCancelled): User cancelled in the purchase overlay
- Code 16 (overlayInvalidConfiguration): Missing or incorrect IAP configuration
- Code 17 (overlayTimeout): Purchase overlay timed out waiting for response
- Code 18 (ineligibleForOffer): User doesn’t meet eligibility criteria for the offer
- Code 19 (unsupportedPlatform): App running on unsupported platform or device
- Code 20 (overlayPresentedInBackgroundScene): Purchase overlay presented in wrong scene
StoreKit 2 error handling
StoreKit 2, introduced in iOS 15, uses a completely different approach to error handling. Instead of error codes, it uses Swift enums with named cases. The key difference is that user cancellation is not an error in StoreKit 2—it’s a separate case in the PurchaseResult enum.
| Scenario | StoreKit 1 (Deprecated) | StoreKit 2 |
| User cancels purchase | SKError.Code.paymentCancelled (2) | PurchaseResult.userCancelled |
| Ask to Buy pending | SKPaymentTransactionState.deferred | PurchaseResult.pending |
| Network error | SKError + NSURLErrorDomain | StoreKitError.networkError |
| Unknown/system error | SKError.Code.unknown (0) | StoreKitError.systemError |
| Product unavailable in region | SKError.Code.storeProductNotAvailable (5) | StoreKitError.notAvailableInStorefront |
| Invalid offer signature | SKError.Code.invalidSignature (12) | Product.PurchaseError.invalidOfferSignature |
| User ineligible for offer | SKError.Code.ineligibleForOffer (18) | Product.PurchaseError.ineligibleForOffer |
| Transaction verification failed | Manual receipt validation | VerificationResult.unverified |
StoreKitError cases
StoreKitError handles general StoreKit errors:
- unknown: An unknown error occurred. Log for debugging, show generic retry message.
- userCancelled: User cancelled the operation. No message needed, return to previous state.
- networkError(Error): Network connection failed. Show connectivity message, allow retry.
- systemError(Error): System-level error. Log underlying error, show generic message.
- notAvailableInStorefront: Product unavailable in user’s region. Hide product or show unavailable message.
- notEntitled: User not entitled to access. Check subscription status.
Product.PurchaseError cases
Product.PurchaseError handles purchase-specific errors:
- invalidQuantity: Invalid purchase quantity. Ensure quantity is > 0.
- productUnavailable: Product not available. Refresh product list.
- purchaseNotAllowed: Device/user cannot make purchases. Inform user to check settings.
- ineligibleForOffer: User doesn’t qualify for offer. Show alternative offer.
- invalidOfferIdentifier: Offer ID not configured. Check App Store Connect.
- invalidOfferPrice: Offer price invalid. Review pricing in ASC.
- invalidOfferSignature: Signature invalid. Regenerate with correct key.
- missingOfferParameters: Required parameters missing. Provide all offer parameters.
Conclusion
Correct error handling improves user experience and can recover users who couldn’t complete their purchase initially. The most common errors you’ll encounter are unknown (0), paymentCancelled (2), and paymentInvalid (3).Error handling and purchase implementation are complex processes. Adapty for iOS makes implementing in-app purchases easier by handling these errors automatically, plus provides subscription analytics, cohort analysis, A/B tests for paywalls, and server-side receipt validation.





