REACT NATIVE IAP · StoreKit & StoreKit 2 – The Definitive iOS Guide
Why This Matters for React Native IAP Developers
If you have shipped iOS purchases with react-native-iap, you know the ritual: Base64 receipt blobs, global listeners scattered across files, flaky Sandbox testers, and a backend endpoint Apple is actively shutting down. StoreKit 2 is not a patch — it is a clean architectural rewrite. Apple rebuilt iOS payments around async/await, replacing every delegate callback with a promise and every opaque receipt with a cryptographically signed JWS (JSON Web Signature) token you can verify on-device without a network call. In react-native-iap v12+ you unlock the entire new model with one config line: storekitMode: “STOREKIT2_MODE”.

SK1 (left): delegate/observer cascade ending at the deprecated /verifyReceipt endpoint. SK2 (right): async/await chain with self-verifiable JWS tokens and the current App Store Server API.
Key Architectural Changes
-
- Transactions are now first-class citizens. Instead of a single monolithic receipt, SK2 gives you individual Transaction objects — signed in JSON Web Signature (JWS) format. Each Transaction carries its own payload: product ID, purchase date, expiry date, quantity, and crucially, a cryptographic signature you can verify on-device without a network call. This alone eliminates the biggest operational risk in SK1.
- Async/await throughout. Every SK2 API uses Swift concurrency. Product.products(for:), Product.purchase(), Transaction.currentEntitlements — all return with await. React Native bridges this naturally through the react-native-iap promise layer, giving you clean async/await in JavaScript.
- Transaction.currentEntitlements. This async sequence delivers every currently-active entitlement for the user — all active subscriptions, all unconsumed consumables, all non-consumables. One call, correct state, no receipt parsing.
JWS vs Receipt — Why Your Backend Changes Too
- SK1 backend: You POST a Base64 receipt blob to /verifyReceipt, parse a deeply nested JSON response, find latest_receipt_info, check expires_date_ms, and grant access. This endpoint is deprecated — Apple will remove it.
- SK2 backend: Each transaction delivers a jwsRepresentation string — a standard ES256-signed JWT. Verify the signature against Apple’s public key at appleid.apple.com/auth/keys. The payload is plain typed JSON: productId, expiresDate, type. No parsing gymnastics. Verifiable on-device or on your server.
Code-level changes in react-native-iap when migrating from SK1 to SK2
- Initialisation & Setup
StoreKit 1
import { setup } from ‘react-native-iap’;
setup({ storekitMode: ‘STOREKIT1_MODE’ });StoreKit 2
import { setup } from ‘react-native-iap’;
setup({ storekitMode: ‘STOREKIT1_MODE’ }); - Fetching Products
StoreKit 1
// getProducts for consumables /
// non-consumables
const products = await getProducts({
skus: [‘com.app.coins’],
});
// getSubscriptions for subs
const subs = await getSubscriptions({
skus: [‘com.app.monthly’],
});
StoreKit 2 // Same API — no change needed // SK2 resolves product metadata
const products = await getProducts({
skus: [‘com.app.coins’],
});
const subs = await getSubscriptions({
skus: [‘com.app.monthly’],
});
// ✓ No code change required here
- Purchase Flow ← Biggest Change
StoreKit 1 — Listener / Callback // Must register GLOBALLY
// before any purchase attempt
const sub = purchaseUpdatedListener(
async (purchase) => {
if (purchase.transactionReceipt){
await sendToServer(
purchase.transactionReceipt
// ↑ raw Base64 blob
);
await finishTransaction(
{ purchase });
}
});
// Trigger purchase separately
await requestPurchase({ sku });
// ⚠ Must remove — leaks if missed
sub.remove();
StoreKit 2 — Async / Await // No listener needed at all
// requestPurchase returns a promise
try {
const purchase =
await requestPurchase({
sku,
andDangerously
FinishTransaction
AutomaticallyIOS: false,
});
// transactionId = JWS token
await verifyJWS(
purchase.transactionId
// ↑ signed JWT, not Base64
);
await finishTransaction(
{ purchase });
} catch (e) { /* handle */ }
Summary
StoreKit 1 served the iOS developer community for fifteen years and deserves respect for enabling an entire economy of premium apps and subscriptions. But its delegate-based, receipt-centric model was always a leaky abstraction — one that required significant server infrastructure and constant maintenance just to answer a simple question: “does this user have an active subscription?”
StoreKit 2 answers that question in three lines of Swift. It eliminates receipt parsing, replaces observer spaghetti with clean async/await, provides on-device cryptographic verification, and ships with an Xcode-integrated test harness that makes TDD for in-app purchases genuinely possible for the first time.
For React Native developers, react-native-iap v12+ exposes all of this through a promise-based API that feels natural. The migration from SK1 to SK2 is non-trivial — especially if you have an existing backend validation layer — but the operational benefits are immediate and compounding. Every new Apple feature from here forward will be SK2 only. The time to migrate is now.
