Sometimes, even creating the perfect app and acquiring users is not enough. The “last mile” issues can rob you of your potential revenue, one of them being iOS errors like SKErrorDomain Error 2. It’s more common than you might think — so common, in fact, that we decided to write a comprehensive guide about it.
Here, we’re going to dive deep into what this error is all about, how it differs between StoreKit 1 and StoreKit 2, and share foolproof solutions to help you fix it in no time. The goal is to enhance your app’s reliability, boost user satisfaction, and eliminate any revenue drainers. Let’s get started.
What is SKErrorDomain Code=2
According to the Apple Developer documentation, SKError 2 is also known as SKError.paymentCancelled. It occurs when a person gets to the payment pop-up (that is, is asked to confirm a purchase or a subscription), and then declines it.
Technically, it’s not an error, as the cancellation is often triggered by the user. However, this error can also pop up regardless of user actions and be caused by an outside factor — and this article covers explicitly such cases.
Important: With iOS 18, Apple has officially deprecated all StoreKit 1 APIs. If you’re still using the original StoreKit framework, now is the time to migrate to StoreKit 2.

StoreKit 1 vs StoreKit 2: How error handling differs
Before we dive into troubleshooting, it’s crucial to understand how payment cancellation is handled differently in each framework version.
StoreKit 1 vs StoreKit 2
| Aspect | StoreKit 1 (Deprecated) | StoreKit 2 (Recommended) |
|---|---|---|
| Error representation | SKErrorDomain Code=2 | PurchaseResult.userCancelled |
| Swift type | SKError.paymentCancelled | Enum case in Product.PurchaseResult |
| Handling mechanism | SKPaymentTransactionObserver delegate | async/await pattern |
| Minimum iOS version | iOS 3+ | iOS 15+ |
| API status | Deprecated in iOS 18 | Actively maintained |
| Verification | Manual receipt validation | Automatic JWS verification |

What can cause SKErrorDomain Code=2
There are several potential triggers for this error. Understanding the root cause is essential for proper handling.
| Cause | Description | Is user action? | Solution |
|---|---|---|---|
| User taps “Cancel” | Explicit cancellation in payment sheet | Yes | Don’t show error |
| Apple ID login cancelled | User cancels during sign-in prompt | Yes | Don’t show error |
| Active subscription exists | Attempting to buy already active product | No | Offer “Restore Purchases” |
| SCA rejection (EU) | Strong Customer Authentication declined | Partial | Inform about retry |
| Ask to Buy declined | Parent/guardian rejected purchase | No | Show “declined” message |
| Network timeout | Connection lost during confirmation | No | Suggest retry |
| Sandbox account issues | Invalid or expired test account | No | Create new sandbox user |
| Payment method invalid | Expired card or insufficient funds | No | Prompt to update payment |
Network issues
Network issues can be a significant catalyst for the SKErrorDomain Code=2. The StoreKit framework heavily relies on consistent network connectivity with little to no lag to perform transactions effectively. Unstable or unreliable connections — for example, using a hotspot or a free VPN — can interfere with the transmission of data.
StoreKit configuration
The StoreKit framework is the core engine driving your in-app transactions, and any mistake in this setup can readily lead to the SKErrorDomain Code=2 error. An incorrectly defined product ID, for example, can trigger the error. Product IDs should match precisely with those defined in App Store Connect; any discrepancy can create conflicts, leading to transaction failures.
Strong Customer Authentication (SCA) in Europe
For users in the European Economic Area, Strong Customer Authentication requires all payments to be verified. In this case, the current transaction may go to a failed state initially. Once payment is approved through the bank’s verification process, a new transaction with the purchased state is created. This behavior can sometimes manifest as a Code=2 error.
All SKError codes reference
For context, here’s the complete list of SKError codes:
| Code | Name | Description |
|---|---|---|
| 0 | unknown | An unknown error occurred |
| 1 | clientInvalid | Client is not allowed to make the request |
| 2 | paymentCancelled | User cancelled the payment request |
| 3 | paymentInvalid | Payment parameters are invalid |
| 4 | paymentNotAllowed | Device is not allowed to make payments |
| 5 | storeProductNotAvailable | Product is not available in the store |
| 6 | cloudServicePermissionDenied | User hasn’t allowed access to cloud service |
| 7 | cloudServiceNetworkConnectionFailed | Couldn’t connect to network |
| 8 | cloudServiceRevoked | User has revoked cloud service permission |
| 9 | privacyAcknowledgementRequired | User must acknowledge privacy policy |
| 10 | unauthorizedRequestData | App is not entitled for request |
| 11 | invalidOfferIdentifier | Offer identifier is invalid |
| 12 | invalidSignature | Cryptographic signature is invalid |
| 13 | missingOfferParams | Required offer parameters are missing |
| 14 | invalidOfferPrice | Offer price is invalid |
| 15 | overlayCancelled | Overlay was cancelled |
| 16 | overlayInvalidConfiguration | Overlay configuration is invalid |
| 17 | overlayTimeout | Overlay request timed out |
| 18 | ineligibleForOffer | User is ineligible for the offer |
| 19 | unsupportedPlatform | Platform doesn’t support this feature |
| 20 | overlayPresentedInBackgroundScene | Overlay presented in background |
How to troubleshoot SKErrorDomain Error 2
Once you’ve run into the issue, it makes sense to go through every detail of the process to make sure everything is good on your part.
| Step | What to check | Method | Expected result |
|---|---|---|---|
| 1 | Product IDs match App Store Connect | Xcode Console logs | No “Invalid product identifier” errors |
| 2 | Transactions finish properly | transaction.finish() calls | All transactions completed |
| 3 | Network is stable | Network Link Conditioner | No timeouts |
| 4 | Sandbox account is valid | Settings → App Store → Sandbox | Account active |
| 5 | iOS/Xcode versions current | About This Mac / Xcode | Latest versions |
| 6 | StoreKit Configuration set up | Product → Scheme → StoreKit Config | Configuration selected |
| 7 | Receipt refresh works | SKReceiptRefreshRequest | Receipt retrieved |
Sandbox environment troubleshooting
Here’s what you should do while in the sandbox environment:
Review StoreKit configuration. Begin by checking your app’s StoreKit configuration. Ensure all product IDs in your code match the ones set in App Store Connect. Misconfigured or mismatched IDs often result in errors in the sandbox environment.
Examine transaction handling. StoreKit handles a sequence of states for each transaction. Each state requires specific handling in your code, and proper handling of these states can eliminate errors. Review your transaction handling code for completeness and accuracy.
Ensure you’re finishing transactions upon success/failure. As we’ve explained before, the general cause of the error occurs at ‘the last mile’. Make sure each payment, even a canceled one, finishes ‘properly’ in your code.
Test network stability. Although it’s a sandbox environment, network conditions still matter. Verify that your testing device has a stable and strong internet connection.
Use Apple’s StoreKit testing tools. Apple provides specific tools to test StoreKit locally in Xcode and the sandbox. These tools allow you to simulate transactions, helping you to identify potential errors. With Xcode 16 and later, you can also test purchase intents for promoted purchases.
Check OS and app versions. Make sure the app and iOS versions are up-to-date. Each new iOS update brings some new terms and rules to the StoreKit, so you would benefit from having the latest one.
Production environment troubleshooting
With real-life scenarios, you get some extra steps — and extra sources of information.
Monitor user feedback. Users can often provide the first indication of an issue. Pay close attention to app reviews, support requests, and social media channels where users might report problems.
Analyze error logs. Use your app’s error logging system to gather information about where and when the error occurs. Troubleshooting becomes that much easier when you can pinpoint a specific page or an SKU that causes the problem.
Make sure the IAP hasn’t been purchased before. One of the key real-life causes reported by the developers occurred when a user tried to purchase an already active subscription. Always have the ‘Restore Purchases’ button at the ready and ask users to try it if they run into any problems.
Check for SCA-related issues. If you have significant user base in the European Economic Area, verify that your app properly handles Strong Customer Authentication flows.
How to submit a bug report
If you’ve exhausted all troubleshooting options and believe you’ve found a StoreKit bug, here’s how to report it:
- Access either the Feedback Assistant website or its app on your Mac.
- Sign in with the Apple ID linked to your Developer Program membership.
- Click the “New Feedback” button.
- Make sure to fill out the feedback form with as much detailed information as you can. Include the specific error (
SKErrorDomain Error 2), during what process the error occurs, and any important pieces of code or log extracts. - Attach any relevant screenshots or data that can provide more context about the problem.
- Once you’ve filled out the form, review your submission for any errors or missing information, then hit “Submit”.
Apple will proceed to examine your feedback. This may take several days, but it’s often rewarded with a potential resolution.

How to prevent SKErrorDomain Code=2
Implementing preventive measures and following best practices can significantly reduce the likelihood of encountering the SKErrorDomain Code=2 error in future projects.
| Practice | Priority | Implementation |
|---|---|---|
| Migrate to StoreKit 2 | Critical | Use async/await APIs for iOS 15+ |
| Validate product IDs | High | Match exactly with App Store Connect |
| Handle all purchase states | High | Include userCancelled, pending cases |
| Implement Restore Purchases | High | Visible button, test regularly |
| Add comprehensive logging | Medium | Track errors by product/step |
| Monitor user feedback | Medium | App reviews, support channels |
| Test in sandbox regularly | Medium | Use fresh sandbox accounts |
| Handle SCA gracefully | Medium | EU-specific messaging |
Recommended implementation
Migrate to StoreKit 2. With StoreKit 1 now deprecated in iOS 18, migrating to StoreKit 2 is no longer optional for long-term app maintenance. StoreKit 2 provides cleaner error handling, automatic transaction verification, and modern Swift concurrency support.
Test StoreKit configuration thoroughly. Test your StoreKit configuration before deploying your app. Make sure that all product IDs match those in App Store Connect and that all transactions process correctly during testing.
Handle all transaction states. Ensure that your code accounts for all possible transaction states. Since SKErrorDomain Code=2 has to do with a ‘canceled’ purchase, make sure your app finishes every transaction properly.
Implement comprehensive error logging. A comprehensive error logging system can help you identify and address issues promptly. By tracking exactly what product or purchase step the errors occur on, you’ll greatly simplify the troubleshooting process.
Regularly review user feedback. Stay on top of user reviews and feedback. Users are often the first to spot issues, and their feedback can help you identify and resolve problems early. Don’t shy away from asking for screenshots or engaging with users on social media.
Keep your app updated. Update your application and ensure it’s compatible with the latest iOS versions. Compatibility issues with newer OS versions can often lead to unexpected errors.
Conclusion
Effectively managing the SKErrorDomain Code=2 error is a critical aspect of maintaining a robust and user-friendly iOS application. This error can disrupt in-app transactions, potentially leading to a diminished user experience and even revenue loss. Luckily, it’s often a minor issue that can be easily tracked and resolved.
With the deprecation of StoreKit 1 in iOS 18, now is the perfect time to migrate to StoreKit 2, which offers cleaner error handling through PurchaseResult.userCancelled instead of error codes, automatic transaction verification, and modern Swift concurrency patterns.
A seamless user experience is the cornerstone of any successful app, and ensuring error-free transactions is an integral part of this. With the insights shared in this article, you’re now well-equipped to manage the SKErrorDomain Code=2, contributing to a more robust and reliable app that users can truly appreciate.
FAQ
PurchaseResult.userCancelled in the purchase result enum. This makes handling cleaner and more intuitive since cancellation is treated as a normal flow rather than an exception. 



