New features and benefits of the new App Store server API

In-App Purchase Apple App Store Tech Backend StoreKit 2

At the WWDC21, Apple has announced a brand new App Store Server API to replace the verifyReceipt endpoint. Let's see what's new and how you could benefit from these APIs.

Quick reminder: the /verifyReceipt endpoint

Until now, you had only one way to get a secure information about your users' purchases: the /verifyReceipt endpoint.

By calling this endpoint, you can get a receipt containing 2 kinds of information:

  • the current renewal statuses of you user's subscriptions
  • the complete history of your user's purchases

The response retrieved from this endpoint looks like this:

{
  "status": 0,
  "environment": "Production",
  "latest_receipt": "MIIVeAYJKoZIhgMC...",
  "latest_receipt_info": [
    {
      "expires_date": "2021-07-11 14:07:20 Etc/GMT",
      "expires_date_ms": "1626012440000",
      "expires_date_pst": "2021-07-11 07:07:20 America/Los_Angeles",
      "in_app_ownership_type": "PURCHASED",
      "is_in_intro_offer_period": "false",
      "is_trial_period": "false",
      "original_purchase_date": "2021-05-11 01:01:21 Etc/GMT",
      "original_purchase_date_ms": "1620694881000",
      "original_purchase_date_pst": "2021-05-10 18:01:21 America/Los_Angeles",
      "original_transaction_id": "380920601869523",
      "product_id": "com.purchasely.monthly",
      "purchase_date": "2021-06-11 14:07:20 Etc/GMT",
      "purchase_date_ms": "1623420440000",
      "purchase_date_pst": "2021-06-11 07:07:20 America/Los_Angeles",
      "quantity": "1",
      "subscription_group_identifier": "20425212",
      "transaction_id": "380920601869772",
      "web_order_line_item_id": "380920686968996"
    },
    {
      "expires_date": "2021-06-11 01:01:17 Etc/GMT",
      "expires_date_ms": "1623373277000",
      "expires_date_pst": "2021-06-10 18:01:17 America/Los_Angeles",
      "in_app_ownership_type": "PURCHASED",
      "is_in_intro_offer_period": "false",
      "is_trial_period": "false",
      "original_purchase_date": "2021-05-11 01:01:21 Etc/GMT",
      "original_purchase_date_ms": "1620694881000",
      "original_purchase_date_pst": "2021-05-10 18:01:21 America/Los_Angeles",
      "original_transaction_id": "380920601869523",
      "product_id": "com.purchasely.monthly",
      "purchase_date": "2021-05-11 01:01:17 Etc/GMT",
      "purchase_date_ms": "1620694877000",
      "purchase_date_pst": "2021-05-10 18:01:17 America/Los_Angeles",
      "quantity": "1",
      "subscription_group_identifier": "20425212",
      "transaction_id": "380920601869523",
      "web_order_line_item_id": "380920686968995"
    }
  ],
  "pending_renewal_info": [
    {
      "product_id": "com.purchasely.monthly",
      "auto_renew_status": "1",
      "auto_renew_product_id": "PURCHASELY_WEEKLY",
      "original_transaction_id": "380920601869523"
    }
  ]
} 

This receipt has many problems that developers struggled with for years.- and that were solved with the new API.


Quickly set-up and launch subscriptions that comply with app stores guidelines to enjoy faster time to revenue and avoid rejections and delays.

Book a demo


The receipt properties

Property names

All the properties were written in snake_case whereas they are in the JSON format, which prefer snakeCase. That's why in the new API, all properties are now written in snakeCase.

-    "transaction_id": "380920601869523",
+    "transactionId": "380920601869523",

Date formats

A same date is represented 3 times whereas you only need 1 format. For example with the expires_date:

"expires_date": "2021-07-11 14:07:20 Etc/GMT",
"expires_date_ms": "1626012440000",
"expires_date_pst": "2021-07-11 07:07:20 America/Los_Angeles",

Moreover, sometimes you only get 1 date in the response, and it doesn't have the correct format. For example, you can get the expires_date_ms format in the expires_date property 🤷‍♂️

"expires_date": "1626012440000",

One more thing on the _ms format: we have a String whereas it should be an Integer.

In conclusion, a cleaner representation of a date would be:

  • a unique date format: _ms as an Integer (and since the format is unique, we don't need the _ms anymore
  • with a camel case name

And that's exactly what we have in the new API:

-    "expires_date": "2021-07-11 14:07:20 Etc/GMT",
-    "expires_date_ms": "1626012440000",
-    "expires_date_pst": "2021-07-11 07:07:20 America/Los_Angeles",
+    "expiresDate": 1626012440000,

The booleans and integers

All the booleans and integers are written as String, which forces the developer to parse the values.

Apple has changed this in its new api and returns the expected format:

-    "quantity": "1"
+    "quantity": 1

The type of purchase

With the old receipt format, you didn't had an easy access to the type of purchase. This is fixed in the new api with the new type field:

"type": "Auto-Renewable Subscription"
  // or "Non-Renewing Subscription"
  // or "Non-Consumable"
  // or "Consumable"

Purchasely is the only SaaS to deliver easy In-App Purchase management from Build, User Interface Management, KPI tracking to robust Reporting Analytics for marketers.

Book a demo


The offer type

With the old receipt format, to determine if an offer was active (or not), you had to search for multiple properties: is_in_intro_offer_period, is_trial_period, promotional_offer_id, offer_code_ref_name. Now, you have all this data consolidated into 2 properties:

-    "is_in_intro_offer_period": "false",
-    "is_trial_period": "false",
-    "promotional_offer_id": "1234567890",
+    "offerType": 2, // 1=introductory_offer, 2=promotional_offer, 3=promo_code
+    "offerIdentifier": "1234567890", // present if offerType = 2 or 3

The cancellation date

The cancellation_date name was confusing and has been renamed into revocationDate:

-    "cancellation_date": "2021-07-11 14:07:20 Etc/GMT",
-    "cancellation_date_ms": "1626012440000",
-    "cancellation_date_pst": "2021-07-11 07:07:20 America/Los_Angeles",
-    "cancellation_reason": "0",
+    "revocationDate": 1626012440000,
+    "revocationReason": 0

A new "appAccountToken" property

Finally, on the application side, developers can associate an account token to the purchase. It can be used to associate your "internal user id" to the purchase and help your servers when they receive the initial purchase S2S.

As a matter of fact, this field is so useful it will be added to the /verifyReceipt API!

"appAccountToken": "12345"

 

And that's all for the properties!

You have now a brand new receipt format:

{
  "appAccountToken": "8024e461-b0a9-33a3-b70e-c615d916ae10"
  "bundleId": "com.purchasely.demo",
  "expiresDate": 1626012440000,
  "inAppOwnershipType": "PURCHASED",
  "offerIdentifier": "com.purchasely.offer1",
  "offerType": 2,
  "originalPurchaseDate": 1620694881000,
  "originalTransactionId": "380920601869523",
  "productId": "com.purchasely.monthly",
  "purchaseDate": 1623420440000,
  "quantity": 1,
  "revocationDate": 1625035210000,
  "revocationReason": 0,
  "subscriptionGroupIdentifier": "20425212",
  "transactionId": "380920601869772",
  "type": "Auto-Renewable Subscription",
  "webOrderLineItemId": "380920686968996"
}

 

Let's look at the 2 new endpoints now.

2 new endpoints

With the old /verifyReceipt api, whenever you want to retrieve the last information about your subscriptions, you have to look for it in 2 places:

  • pending_renewal_info: where you can find information about the incoming transactions of your user's subscriptions (ie: what's coming at the next renewal date)
  • latest_receipt_info: where you can get the complete history of your user's subscriptions.

It would be perfect if:

  • the pending_renewal_info was not that big (it contains all the transactions ever created)
  • the pending_renewal_info was easy to browse (if you want to get the last transaction, you have to sort the array since it's not ordered)

💡 The elements in latest_receipt_info are not ordered and you have to sort this array to get the last transaction. Since that's not obvious, many developers don't know it and aren't looking at the right transaction to make their decisions!

 

To solve these issues, Apple has created 2 new endpoints:

These 2 endpoints have the new properties described before. Their API is pretty straight forward and we won't go into details. The only hard part concerns the new JWS format used to make API calls to Apple and decode the responses. But don't worry, we've got you covered (read our article How to handle JWS signature) !

Ready to increase your in-app revenue?

 
BOOK A DEMO