Apple In-App Purchases


Apple mandates that payments for all digital goods within iOS apps be completed using their IAP platform. Digital goods include access to premium features as well as consumable tokens/credits.

In-app purchases provide additional channels to monetize your app. iOS contains these types of in-app purchases:

  • Consumables: can be purchased multiple times, e.g. 100 digital coins
  • Non-consumables: can only be purchased once, e.g. a particular character skin, or permanent advertising removal
  • Non-renewable subscriptions: lasts for a fixed amount of time and then expires, e.g. 1-year premium access
  • Renewable subscriptions: automatically renews, e.g. monthly premium access

Generally, if your app accepts payment for any digital goods or memberships, Apple’s rules require you to accept in-app purchases as the only payment method within your mobile app.

GoNative’s in-app purchase flow has these steps:

  1. Create IAP in App Store Connect
  2. App gets a list of purchasable items from your site and verifies with the store
  3. Site shows UI for purchasable items
  4. User starts purchase.
  5. Purchase is verified and fulfilled

If using server-side verification, app posts receipt to your site. Web server verifies receipt with Apple and fulfills purchase.
If using client-side verification, purchase data is available via JavaScript.

Implementation Guide

Once the premium module has been added to your app, you may use the following GoNative JavaScript Bridge commands to access its functionality.

Create your app on App Store Connect, picking a globally unique bundle ID.

Open your new app on App Store Connect, go to App Store -> In-App Purchases.

Create your In-App purchase. The Product ID is a string used to identify the IAP. For auto-renewing types, there is a concept of "subscription groups". Users can only be subscribed to one IAP in each subscription group. For example, you may offer a monthly or semiannual membership. It would only make sense for a user to be subscribed to one, not both. There is additional information used to describe the IAP, and notes for Apple's review. You must create a user-friendly description in at least one localized language.

At the IAP screen, click "View Shared Secret". Save this string.

Create a JSON file that lists the product IDs you would like to offer for sale. This allows you to in real time add or remove products from sale without publishing a new version of your app. Apple requires products IDs be alphanumeric with dots, dashes and underscores are allowed. We recommend prefixing with your Bundle ID to aid in reporting. The following example shows two products for sale, com.appname.subscription_monthly and com.appname.subscription_yearly.

    "products": [

You must then host the JSON file on your website to be accessible by your app. We are using

Now you are ready to enable and configure the module. You will need:

  • productsUrl - The productsUrl should point to the JSON file on your website. When your GoNative app launches, it will make an HTTP GET to your productsUrl.
  • postUrl - The postUrl should be provided if you are using server-side verification (explained below). It can be omitted if you would like to handle the purchases on the device.

The app will verify the list of product IDs with App Store to ensure they are all available for purchase. Then it will execute a javascript function on your website called gonative_info_ready, with a single object parameter:

    inAppPurchases: {
        platform: 'iTunes',
        canMakePurchases: true,
        products: [{
            productID: 'product_id',
            localizedDescription: 'Description from iTunes',
            localizedTitle: 'Title from iTunes',
            price: 9.99,
            priceLocale: 'en-US',
            priceFormatted: '$9.99'

On iOS, platform will always be iTunes. canMakePurchases may be false if disabled via parental controls, or due to other reasons (see Testing process). Products is an array generated from productsUrl, filtered to show only purchasable items and with additional fields added.

You should create a page on your website that shows the available items for purchase. It should wait for the gonative_info_ready function to be called and then populate the available items for purchase and display. Show the price using the priceFormatted string, as users may have different language and currency settings.

↔️GoNative JavaScript Bridge

When a user decides to purchase an IAP, run the JavaScript function:

gonative.purchase({'productID': 'product_id'});

The GoNative app will then start the in-app purchase flow.

Server-side verification

There are two methods available to fulfill your user’s purchases: server-side, and on-device. Server-side verification is generally recommended if purchases are to be associated with a user account, as it is more secure. When an in-app purchase is made, the purchase data will be sent to your web server, which will credit or fulfill the purchased item after it has verified the purchase with Apple. During this process, you should associate the purchase with the logged-in user in your system.

For example, your website may have user logins with a free membership tier, and a premium membership tier. In this case, you should only display the purchase page within the logged-in section of your website. When the purchase is made, the receipt data will be POSTed the configured postUrl with the same cookies as the logged-in user.

The JSON POST to postUrl will contain the contents:

    "receipt-data": "xxxxxxxxxxxxxxxxxxx"

Your web server needs to create a post to with the contents:

    "receipt-data": "xxxxxxxxxxxxxxx",
    "password": "shared secret from iTunes connect",
    "exclude-old-transactions": true

If exclude-old-transactions is set to true, Apple will only return the latest transaction for auto-renewing subscriptions. Otherwise, you will get back the entire history of subscriptions.

Apple's server should return HTTP status 200 with a JSON object (see example below). If the JSON object is {"status":21007}, the receipt was generated from the sandbox/test environment. In that case, re-do the POST to the following url:

Assuming the response from Apple’s server has status 0, verify the receipt's bundle_id matches your app, and what products have been purchased. Additionally, save the receipt-data in your database so that you can verify successful auto-renews. The receipt-data serves as a “token” you can use to get updated subscription information. At this point, your server should provide whatever it is the user has purchased (premium content, virtual currency, etc.)

Your server should respond with a JSON object:

    "success": true,
    "title": "Thank you for your purchase!",
    "message": "Your IAP has been credited to your account",
    "loadUrl": ""

The GoNative app will notify App Store that the purchase has been fulfilled and show the user your message. loadUrl is an optional field, which the app will open and show to the user if received.

If the status in the JSON is any value other than 0, or Apple’s endpoint does not return an HTTP status 200, or the request to Apple fails, do not fulfill the purchase. See for other possible JSON status values. Your web server should respond with a JSON object with success set to false. We recommend logging the response from Apple for troubleshooting purchases, especially the status field.

On failure, your web server may supply a message, title, and loadUrl to provide feedback to your user. You may choose to surface the status value from Apple to your user in a message. The app will re-attempt the POST to your web server each time it launches until it gets a success. If a purchase is not ever fulfilled, Apple will eventually refund the user.

An example response from App Store Connect will look as follows:

    "status": 0,
    "environment": "Sandbox",
    "receipt": {
        "receipt_type": "ProductionSandbox",
        "adam_id": 0,
        "app_item_id": 0,
        "bundle_id": "",
        "application_version": "1.0.0",
        "download_id": 0,
        "version_external_identifier": 0,
        "receipt_creation_date": "2016-12-01 22:26:23 Etc/GMT",
        "receipt_creation_date_ms": "1480631183000",
        "receipt_creation_date_pst": "2016-12-01 14:26:23 America/Los_Angeles",
        "request_date": "2016-12-02 02:31:44 Etc/GMT",
        "request_date_ms": "1480645904550",
        "request_date_pst": "2016-12-01 18:31:44 America/Los_Angeles",
        "original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
        "original_purchase_date_ms": "1375340400000",
        "original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
        "original_application_version": "1.0",
        "in_app": [{
            "quantity": "1",
            "product_id": "member_basic_m",
            "transaction_id": "1000000255553673",
            "original_transaction_id": "1000000255553673",
            "purchase_date": "2016-12-01 22:26:22 Etc/GMT",
            "purchase_date_ms": "1480631182000",
            "purchase_date_pst": "2016-12-01 14:26:22 America/Los_Angeles",
            "original_purchase_date": "2016-12-01 22:26:23 Etc/GMT",
            "original_purchase_date_ms": "1480631183000",
            "original_purchase_date_pst": "2016-12-01 14:26:23 America/Los_Angeles",
            "expires_date": "2016-12-01 22:31:22 Etc/GMT",
            "expires_date_ms": "1480631482000",
            "expires_date_pst": "2016-12-01 14:31:22 America/Los_Angeles",
            "web_order_line_item_id": "1000000033828184",
            "is_trial_period": "false"
    "latest_receipt_info": [{
            "quantity": "1",
            "product_id": "subscription1",
            "transaction_id": "1000000255546758",
            "original_transaction_id": "1000000255546758",
            "purchase_date": "2016-12-01 21:28:05 Etc/GMT",
            "purchase_date_ms": "1480627685000",
            "purchase_date_pst": "2016-12-01 13:28:05 America/Los_Angeles",
            "original_purchase_date": "2016-12-01 21:28:05 Etc/GMT",
            "original_purchase_date_ms": "1480627685000",
            "original_purchase_date_pst": "2016-12-01 13:28:05 America/Los_Angeles",
            "is_trial_period": "false"

On-device verification

An alternative to server-side verification is on-device verification. This can be used if your app does not have user login accounts to keep track of users using different devices. It is less secure than server-side verification, as someone with a jailbroken iPhone could theoretically modify your app and make it act as if a purchase has been made.

To use on-device verification only, do not provide a postUrl in your app’s config file. When your app launches and when purchases are made, the app will execute a javascript function you have defined called gonative_iap_purchases with the following data:

  "hasValidReceipt": true,
  "platform": "iTunes"
  "activeSubscriptions": ["member_basic_w"],
  "allPurchases": [
      "purchaseDateString": "2019-08-11T15:53:13Z",
      "transactionIdentifier": "1000000556506948",
      "webOrderLineItemID": 1000000046196920,
      "originalPurchaseDateString": "2019-08-07T23:46:15Z",
      "quantity": 1,
      "productIdentifier": "member_basic_w",
      "originalTransactionIdentifier": "1000000555471857",
      "cancellationDateString": "",
      "subscriptionExpirationDateString": "2019-08-11T15:56:13Z"
      "purchaseDateString": "2019-08-11T16:03:44Z",
      "transactionIdentifier": "1000000556507336",
      "webOrderLineItemID": 1000000046196984,
      "originalPurchaseDateString": "2019-08-07T23:46:15Z",
      "quantity": 1,
      "productIdentifier": "member_basic_w",
      "originalTransactionIdentifier": "1000000555471857",
      "cancellationDateString": "",
      "subscriptionExpirationDateString": "2019-08-11T16:06:44Z"

Check that hasValidReceipt=true. The allPurchases field is an array of objects containing information on what that user’s device has purchased. For convenience, we provide an activeSubscriptions array that lists the product IDs of what subscriptions are currently active.

Parse the data in your gonative_iap_purchases javascript function and provide any appropriate functionality. For example, you may choose to show ads in your app if the user has not purchased a premium add-removal nonconsumable purchase or recurring subscription.

Restoring Purchases

If you are offering subscriptions or non-consumable in-app purchases, you should add a “Restore Purchases” button somewhere in your app. This allows your users to continue to use their previous purchases if they change devices or have multiple devices.

↔️GoNative JavaScript Bridge

To restore purchases, run the JavaScript function:


Any previous purchases will be restored and will start the verification process outlined above, as if they were newly purchased. If there are no purchases to restore, no action is performed. Unfortunately, it is not possible to differentiate between a lack of purchases to restore, a delay, or a failure to restore. You may choose to display a message such as “Restore requested. If you have any previous purchases, they will be available shortly.”

Auto-renewable Subscriptions

Apple will automatically bill users who have purchased auto-renewable subscriptions. To check on the status of a user’s subscription, POST the receipt again to Apple’s endpoint and check the latest_receipt_info field. You may wish to set up a regular job to go through all active subscriptions.

Apple can also notify you of subscription status changes by posting to an endpoint you have set up to handle the change events. Go to App Store Connect -> Your App -> App Information and enter the URL. See the “Status Update Notifications” section in the In-App Purchase Programming guide:

Additional References

Testing process

To test your in-app purchases, you will need to create App Store sandbox users under the App Store Connect account that owns your app. The users must not already exist in the Apple system, i.e. you cannot use your regular account logins. You can enter test data for most of the fields but may need to set passwords that are at least 10 characters with uppercase, lowercase, and numbers.

When the app launches or a purchase is initiated, you will be prompted to sign in with the test user’s account. The purchase flow can be tested without real payment. Auto-renewing subscriptions will renew at an accelerated rate (5 minutes per month) for 6 times, and will then cancel.

If you are experiencing issues testing iOS purchases, especially if canMakePurchases is false, please review the following:

  • Ensure you are building with the latest release (non-beta) Xcode version
  • Verify that the “In-app purchase” capability has been added to your app in Xcode under Signing & Capabilities.
  • Do not use a sandbox login to sign in directly to iCloud on your device. Only use the sandbox login when the in-app purchase is prompted on your device
  • Make sure that you are using a sandbox login created under your account on App Store Connect
  • Verify that parental controls are not activated that may prevent purchases.
  • Reboot your device


In-App Purchase Testing

Each of the above are potential causes of issues when testing In-App Purchases. Check each point carefully and make use of our demo page at