Validating In-App purchase at server side

As we know, Apple lets users purchase goods and services in apps with Apple IDs and all in-app purchases need to be validated. There are two ways to verify these purchases:

  1. via the App Store, through a secure connection between your app and your server, or
  2. locally. Local verification can be used for simple apps that don’t require a server. But here you run into security risks: local purchases can be forged on hacked iPhones. Therefore, using your own server to communicate with the App Store is generally the best option. In this case, your app will recognize and trust only your server letting you control all transactions between your server and user devices and will help persisting in-app purchases to maintain and manage purchase records.

Getting Started

To validate purchase at server side we will use in-app purchase library. Its a node.js module for in-app purchase (in-app billing) and subscription for Apple, Google Play, Amazon Store, Roku, and Windows.


npm install in-app-purchase

In order to verify your in app purchase/auto-renew subscriptions you need to submit request with JSON content to the App store.

  • receipt-data(Required) - The Base64 encoded receipt data. (This will generate by App Store after purchase is made).
  • password(Required) - Your app's shared secret (a hexadecimal string) (Use this field only for receipts that contain auto-renewable subscriptions.)
  • exclude-old-transactions - Set this value to true for the response to include only the latest renewal transaction for any subscriptions.

Validation in iOS through App Store

var iap = require('in-app-purchase');
iap.config({
  applePassword: '[shared secret from iTunes Connect]'
});
iap.setup(function (error) {
  if (error) {
   console.log(error)
  }
  iap.validate(receipt, function (error, response) {
    if (error) {
      console.log(error)
    }
    if (iap.isValidated(response)) {
       console.log(response)
    }
  });
});

As of v1.4.0+ of in-app-purchase .validate and .validateOnce detects service automatically from the receipt.

  • When auto renew subscription purchase is successfully validated then it'll give response as below:
receipt: 
   { receipt_type: 'ProductionSandbox',
     adam_id: 0,
     app_item_id: 0,
     bundle_id: 'your_bundle_data',
     application_version: '0.0.5',
     download_id: 0,
     version_external_identifier: 0,
     receipt_creation_date: '2020-01-18 06:44:42 Etc/GMT',
     receipt_creation_date_ms: '1579329882000',
     receipt_creation_date_pst: '2020-01-17 22:44:42 America/Los_Angeles',
     request_date: '2020-01-18 06:44:48 Etc/GMT',
     request_date_ms: '1579329888711',
     request_date_pst: '2020-01-17 22:44:48 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: [{...your_in-app_purchases} ] },
  latest_receipt_info: 
   [ {...expired auto-renew receipt information},
    { quantity: '1',
       product_id: 'your_product_id',
       transaction_id: '1000000616XXXXXX',
       original_transaction_id: '1000000616XXXXXX',
       purchase_date: '2020-01-18 06:34:05 Etc/GMT',
       purchase_date_ms: '1579329245000',
       purchase_date_pst: '2020-01-17 22:34:05 America/Los_Angeles',
       original_purchase_date: '2020-01-17 10:49:26 Etc/GMT',
       original_purchase_date_ms: '1579258166000',
       original_purchase_date_pst: '2020-01-17 02:49:26 America/Los_Angeles',
       expires_date: '2020-01-18 06:39:05 Etc/GMT',
       expires_date_ms: '1579329545000',
       expires_date_pst: '2020-01-17 22:39:05 America/Los_Angeles',
       web_order_line_item_id: '10000000XXXXXXX',
       is_trial_period: 'false',
       is_in_intro_offer_period: 'false',
       subscription_group_identifier: '2059XXXX' },
     { quantity: '1',
       product_id: 'your_product_id',
       transaction_id: '100000061XXXXXX',
       original_transaction_id: '1000000616XXXXXX',
       purchase_date: '2020-01-18 06:44:41 Etc/GMT',
       purchase_date_ms: '1579329881000',
       purchase_date_pst: '2020-01-17 22:44:41 America/Los_Angeles',
       original_purchase_date: '2020-01-17 10:49:26 Etc/GMT',
       original_purchase_date_ms: '1579258166000',
       original_purchase_date_pst: '2020-01-17 02:49:26 America/Los_Angeles',
       expires_date: '2020-01-18 06:49:41 Etc/GMT',
       expires_date_ms: '1579330181000',
       expires_date_pst: '2020-01-17 22:49:41 America/Los_Angeles',
       web_order_line_item_id: '100000004XXXXXX',
       is_trial_period: 'false',
       is_in_intro_offer_period: 'false',
       subscription_group_identifier: '2059XXXX' } ],
  latest_receipt: 'IWontTellYouMyReceiptData......xyz',
  pending_renewal_info: 
   [ { auto_renew_product_id: 'your_product_id',
       original_transaction_id: '1000000616XXXXXX',
       product_id: 'your_product_id',
       auto_renew_status: '1' } ] }

Enabling Server-to-Server Notifications

  • Server-to-server notifications are a service for auto-renewable subscriptions. The App Store sends notifications to your server of real-time changes in a subscription's status.
  • Server-to-server notification service is recommended, especially if you offer subscription services across multiple platforms and you need to keep the subscription records updated. You can use notifications along with receipt validation to validate a user's current subscription status and provide them with services or promotional offers based on that status.
  • In order to start this service you have to enable your Subscription Status URL on App Store Connect, you'll automatically receive server notifications on your secure server.
  • The App Store delivers JSON objects via an HTTP POST to your server for notable subscription events. The App Store posts notifications for subscription events represented by the values of the notification_type field.