iOS Development

StoreKit 2 in 2026: Products, Purchases, and Transaction Verification

May 2026 · 12 min read

StoreKit 2 is now the standard for in-app purchases on Apple platforms. The original StoreKit (SK1, delegate-based, receipt JSON verification, manual transaction management) still works but is increasingly painful to maintain. SK2 replaces it with async/await, JWS-signed transactions you can verify locally, and a much cleaner mental model.

This guide covers the production-ready StoreKit 2 patterns for 2026: loading products, initiating purchases, verifying transactions, handling renewals and billing retry, and the critical rule about finishing transactions that catches almost every first-time SK2 user.

Loading products and initiating purchases

The basic loading pattern is async and takes a Set of product IDs you registered in App Store Connect. Call Product.products(for: productIDs), await the result, and you get back a [Product] array with prices, titles, descriptions and subscription metadata pre-localised for the current StoreFront.

Purchase is also async. Call product.purchase() and await the result. The result is a PurchaseResult enum with three cases: success (with a VerificationResult), userCancelled, and pending (the parent or organisation has to approve). Pattern-match on the result, extract the verified transaction, update your purchased products state, then - critically - call transaction.finish().

Two non-negotiable rules emerge from this pattern:

Rule 1: Transaction.updates listener MUST start at app launch. Not when the paywall is shown, not lazily. If you don't start the listener at launch, transactions that arrive during the launch window (Ask to Buy approvals, family sharing changes, sandbox renewals) are silently lost. The standard pattern is to spin up a Task in your StoreManager init that iterates over Transaction.updates indefinitely.

Rule 2: Every verified transaction MUST be finished. If you skip the transaction.finish() call, the transaction is re-delivered to your app on every subsequent launch, forever. Users will see purchase dialogs again, restored entitlements may overwrite local state, and your analytics will be polluted. This is the single most common SK2 bug.

Verification, entitlements, and billing retry

StoreKit 2 transactions arrive wrapped in VerificationResult<Transaction>. The SDK has already done JWS signature verification - but you should still pattern-match the result to extract the unverified vs verified state explicitly. Write a small helper checkVerified function that throws if the result is unverified and returns the unwrapped value if verified.

To get the user's current entitlements, iterate over Transaction.currentEntitlements. This returns the latest transaction for every product the user currently has access to. Build a Set<String> of product IDs and use it to drive your UI - check premium feature gates against this set.

Grace periods and billing retry. When a subscription renewal fails (expired card, insufficient funds), Apple gives the user a grace period (6-16 days configured in App Store Connect) where they retain access while Apple retries billing. After grace, billing retry continues for up to 60 days. Check transaction.subscriptionStatus to see the current state. The states .subscribed, .inGracePeriod and .inBillingRetryPeriod should all grant access; .expired and .revoked should deny.

Granting access during .inGracePeriod and .inBillingRetryPeriod reduces involuntary churn dramatically - users often don't notice their card expired, and cutting them off creates support tickets and refund requests. This is one of those non-obvious patterns that separates apps with healthy renewal rates from apps that bleed users for accidental reasons.

Restore purchases. StoreKit 2 replaces the old restoreCompletedTransactions with AppStore.sync(). Call it when the user taps a "Restore Purchases" button. After sync, re-read Transaction.currentEntitlements to pick up any restored items. Most apps include a Restore button on the paywall - it's a Guideline 3.1.1 requirement for subscription apps.

Quick comparison

Option Best for Cost / effort Notes
Product.products(for:) Load product catalogue Async one-shot Cache for session
product.purchase() Initiate purchase User-facing dialog Returns VerificationResult
Transaction.updates App-launch listener Async sequence MUST start at launch
transaction.finish() Acknowledge Required Or transaction re-delivers

Common pitfalls and how to avoid them

Across every domain this article touches, the same shape of mistake recurs. Practitioners new to the field overweight the most visible piece of the system — the screenshot, the paywall, the exam question, the headline price — and underweight the underlying constraint that actually determines outcomes.

The five most common failure modes:

  1. Optimising for the demo, not the durability. A working demo in a controlled environment proves nothing about reliability under real conditions. In iOS development, an in-app purchase flow that works in the Xcode Simulator says nothing about how it behaves in App Store sandbox with network latency and Ask to Buy approvals. In an exam, a 100% score on an untimed quiz tells you nothing about whether you can do 49/50 in 45 minutes with no second guesses. Build for the hardest realistic case from the start.
  2. Skipping the first-principles documentation. Every system has a canonical specification. App Review Guidelines for iOS, the official EU regulations for tax deductibility, the CITB question bank for CSCS, the OMIE market rules for Spanish electricity. Reading them takes a few hours but saves weeks of wrong-direction work. Secondary sources (blogs, tutorials, this article included) are useful as orientation but never authoritative.
  3. Ignoring the rate limit. Every external system has rate limits — explicit (APNs silent push throttling, RevenueCat API quotas, exam retake fees) or implicit (App Review patience, customer attention spans, your own working memory). Plan around them. A workflow that requires more rate-limited operations than the system allows will fail in production, not on day one but during the first stress event.
  4. Underweighting localisation and regional variation. What is true for Germany is not always true for Italy. What is true for English-speaking users is not always true for Japanese ones. What is true for the UK CSCS test is not always true for the Irish equivalent. Always check the local rule before applying a general one.
  5. Treating the documentation as static. Apple updates App Review Guidelines. The Bundeslaender change Schonzeiten. OMIE adjusts market clearing algorithms. Set up a periodic review (quarterly is enough for most things) and re-read the canonical sources. Workflows that worked perfectly a year ago can be silently broken today.

None of these are dramatic. The dramatic mistakes (catastrophic bugs, audit findings, exam failures) are the visible tip of a longer-running iceberg of small misses. Catching the small misses is what separates routine outcomes from problematic ones.

Key takeaways

  • Product.products(for:) — Load product catalogue. Cache for session.
  • product.purchase() — Initiate purchase. Returns VerificationResult.
  • Transaction.updates — App-launch listener. MUST start at launch.
  • transaction.finish() — Acknowledge. Or transaction re-delivers.

The pattern that runs through every section above: start with the constraint, not the wishlist. In an exam, the constraint is the question bank and the pass mark. In an electricity market, it is the auction clearing rule. In a tax workflow, it is the receipt-retention requirement. In a code architecture, it is the platform's design decision (StoreKit's transaction lifecycle, App Review's guideline, APNs's authentication model). Get the constraint right and the rest follows.

The opposite failure mode — designing for an aesthetic ideal, then trying to retro-fit the constraint — is the most common cause of wasted work in every domain covered here. A beautiful paywall that hangs in sandbox is rejected at App Review. A polished freelancer expense report that lacks receipts is disallowed by the tax office. A study plan that ignores the actual question distribution leaves the candidate stuck below the pass mark.

The practical recommendation: read the official rules of whatever system you are operating in, extract the binding constraints, and treat them as inputs to the design — not afterthoughts. Every section of this article is the application of that principle to a specific domain.

FAQ

What's the difference between StoreKit 1 and StoreKit 2?
SK2 uses async/await instead of delegates, JWS-signed transactions you can verify locally instead of receipt-server roundtrips, and Transaction.currentEntitlements instead of restoreCompletedTransactions. It's a cleaner API and what Apple is investing in going forward.

Do I still need a server for receipt verification?
Not strictly. SK2 transactions are JWS-signed by Apple and can be verified entirely on-device. A server is still useful for cross-device entitlement sync, anti-fraud and analytics - but it's no longer required for basic verification.

What happens if I forget to call transaction.finish()?
The transaction is re-delivered to your app on every launch indefinitely. Users will see purchase dialogs again, restored entitlements may overwrite local state, and your analytics will be polluted. It's the #1 SK2 bug.

Should I use RevenueCat instead of StoreKit 2 directly?
If you want cross-device entitlement sync, paywall A/B testing, analytics dashboards and webhook hooks for free, RevenueCat is worth it. If you only need on-device entitlement checks and have one platform, direct SK2 is simpler. Many apps use both.

Further reading and references

The references below cover the official sources for the rules cited in this article. Where applicable, they include the canonical documentation, regulatory text, or vendor-provided guides. For each one, prefer the official source over secondary commentary — secondary sources go stale fast and frequently misquote the binding rule.

  • Official documentation of the system in question (linked from each app or service's own help centre).
  • Apple Developer Documentation for any iOS/macOS reference — the WWDC session videos and the corresponding Human Interface Guidelines pages are the authoritative source.
  • For EU regulatory questions (taxation, data protection, energy market structure), consult the relevant national authority — most publish their guidance in English.
  • For Spain and Italy energy market data, OMIE and GME both publish full historical price series in CSV format from their public websites — no API key required.
  • For UK CSCS prep, the CITB publishes the official question bank book each year — buy a current copy if you want the authoritative source.

If you find a contradiction between this article and an official source, the official source wins. Article rules of thumb are summaries — they have edge cases, exceptions, and regional variations that the source documents specify exactly.

iOS development services

Production iOS app development with StoreKit 2, RevenueCat and AppFoundation. 12 apps shipped.

Book a discovery call

Related Posts

SwiftUI NavigationStack in 2026
Typed routes, deep links, tab navigation.
App Store rejection reasons
The 12 most common reasons and how to fix each.
← All blog posts

Ship iAP correctly

I've shipped StoreKit 2 in 12 production apps. Let's get yours right the first time.

Book a discovery call