REACT NATIVE IAP · StoreKit & StoreKit 2 – The Definitive iOS Guide

13 / Mar / 2026 by Nikhil Singh 0 comments

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”.

na

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.

FOUND THIS USEFUL? SHARE IT

Leave a Reply

Your email address will not be published. Required fields are marked *