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.
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.
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) !