Skip to main content
Infrastructure

Receipt Validation in StoreKit 1 vs StoreKit 2: Exploring the New App Store Server API to Validate Receipts

Receipt Validation in StoreKit 1 (the Original StoreKit) was done with verifyReceipt Endpoint. Now, in StoreKit 2 there's App Store Server API, learn more

Suren

Suren

November 29, 20249 min read
verifyreceipt vs app store server api  storekit 2

Hi there, I’m Suren, an SDK engineer at Qonversion. I work a lot on making subscription infrastructure setup seamless for our users and I always follow the updates that happen in and around iOS.

This is the second part of our migration guide from Original StoreKit to StoreKit 2 where we'll mainly discuss the receipt validation process. We'll dig into the differences for receipt validation in StoreKit 1 where we used verifyreceipt endpoint and StoreKit 2 where we refer to App Store Server API.

We will examine the differences in data between these two approaches and how this can help you address your business needs and accurately determine the user’s status and access within your application. Additionally, I will touch on some important aspects of the migration process that you won’t find in the documentation but will unfortunately encounter in practice.


Before getting to receipt validation, go through these if you haven't:

Receipt Validation in StoreKit 1 with verifyReceipt EndpointReceipt validation occurs using the endpoint verifyReceipt. The receipt contains all the information about the transactions made by the user (specific Apple ID) and you need App Store Connect Shared Secret for implementation.

First, let’s look at the types of in-app purchases:

  • Subscriptions
  • Non-consumable purchases
  • Consumable purchases

Subscriptions and non-consumable purchases appear in the receipt and remain there permanently. Generally, to grant access to your users, you only need to check if such a transaction exists in the receipt and provide the necessary access.

What about consumable purchases? This is a bit more complex. Such a transaction will be stored in the receipt only until you call finish() on the StoreKit side. We discussed this in the previous article. Therefore, it’s crucial to call finish() only after you have processed the transaction and granted all necessary access. For consumable in-app purchases, this often involves something like in-game currency, crystals, and so on.

Now let’s take a closer look at the receipt and understand what data we can extract from it.

After you execute the verifyReceipt request, you will receive a decrypted receipt in the response. The most interesting fields are:

responseBody.Latest_receipt_info: This is an array that contains all in-app purchase transactions.responseBody.Receipt.In_app: This is an array that contains the in-app purchase receipt fields for all in-app purchase transactions.

>

You might think there's no difference in these. But trust me, you'll need to check both of these arrays for transactions because, in practice, we found that this is the most reliable approach.

Let’s examine the data using the example of a subscription. I’ve taken it from responseBody.Latest_receipt_info.From what we find here, the most interesting field is expires_date, which allows us to determine whether to grant the user access. The other fields can be used for analytics if needed, such as indicating that the purchase was made using a promotional offer or that the user is in a trial period.As you can see, there is no price included, and you can only obtain it from StoreKit by retrieving the product. An important point is that you can get the product price only from the SKProduct object in the Original StoreKit, and this will be the current price of the product. If there have been any price changes, that information will not be available.Regarding non-consumable in-app purchases, a key difference from the previous example is that there is no expires_date because non-consumable purchases are used to grant permanent access. For example, items like cars or weapons in a game, or options to disable ads. Therefore, you simply need to find a transaction with the corresponding product_id and grant access to the user.We have a comprehensive guide that thoroughly discusses the structure of the receipt and everything you need to know. Please refer to that for details. Now, let’s move on to the alternative to verifyReceipt, specifically the new App Store Server API.

Receipt Validation in StoreKit 2 with App Store Server API

For receipt validation with App Store Server API, we will have several options depending on what we want to obtain.

Get transaction info

If we only need data about a specific transaction, this endpoint will return information solely about the particular transaction for which we made the request.

If we need to update the status of all subscriptions and accesses for the user within our application, this is an excellent candidate. This endpoint will return the statuses for all of a customer’s auto-renewable subscriptions in your app.

This option can cover all possible situations. This endpoint will return the customer’s in-app purchase transaction history for your app. In other words, it provides all transactions made by the user within your application. To make the request, you only need to provide the identifier of any transaction made by that user.

The responses from these endpoints will differ, but ultimately, they all converge into the JWSTransactionDecodedPayload object. It contains a wealth of useful information. It includes everything we saw earlier in the receipt, along with much more.From the data in the JWSTransactionDecodedPayload, you can also find the expiration date, whether a trial or promotional offer was used, and which specific promotional offer was applied.The best is that this object includes the price and currency of the transaction. As we discussed with receipts, we could only obtain the product price from SKProduct, which represents the current price. We wouldn’t know what the price was at the time of the transaction. However, in the new App Store Server API, the price and currency are tied to the transaction itself, allowing us to understand the price at the moment the transaction was made. Moreover, if the transaction was made using a promotional offer, the price will reflect that.

Another nice bonus is that you can determine the type of in-app purchase directly, eliminating the need to deduce it from additional factors like the presence of an expiration date or by comparing product IDs.

In addition to the main set of fields mentioned above, you can find a number of fields that you can also obtain from this object or, for example, from renewal information payloads.

Of particular interest, perhaps, is the presence of information about:

Migrating from Original StoreKit to StoreKit 2

We see that the new App Store Server API offers much more functionality compared to the old verifyReceipt. We can use different endpoints for specific purposes or one that returns all the necessary data. We can retrieve all the same information as before, plus a wealth of new fields that will simplify our work.Now, what's the best approach to migration? Is it possible to first migrate to StoreKit 2 while still using verifyReceipt, or should we migrate to the App Store Server API while remaining on the Original StoreKit, or should we do everything at once? Let’s break it down.

Initially, all options seem feasible, but in practice, that’s not entirely the case.

On one hand, yes, you can migrate to StoreKit 2 first and continue using verifyReceipt. With StoreKit 2, you still have the option to obtain receipts. However, this is a poor choice in practice. We encountered issues where not all transactions made with StoreKit 2 immediately appear in the receipt.

Despite Apple promising proper synchronization between the two StoreKit versions during their initial WWDC presentation, this isn't always been the case. Yes, the transaction will eventually appear in the receipt, but not always right away. The wait for it to show up can take a long time, which is unacceptable for both you and the user who is still waiting for their access.

Therefore, I suggest completely disregarding this option from the start if you were considering it.

What about the reverse situation: migrate to the App Store Server API first, and then to StoreKit 2? This option seems quite practical and convenient. You can take any transaction from the receipt and make a request to the App Store Server API with that data to get all the necessary information about the transaction or all transactions for the user. This is what Apple mentions in their presentations, and it holds true in practice. There are no surprises here.

Lastly, the third option — migrating simultaneously to both the App Store Server API and StoreKit 2 — is also a viable approach. Base your decision on the availability of your resources.

Key Takeaways on Receipt Validation

In StoreKit 1, the verifyReceipt endpoint retrieves all transaction data but lacks key fields like price at the time of purchase. This requires fetching additional details from the SKProduct object.

In StoreKit 1, Validation involves examining fields like expires_date for subscriptions and product_id for non-consumable purchases.

App Store Server API provides comprehensive endpoints such as Get Transaction Info, Get All Subscription Statuses, and Get Transaction History, tailored to specific use cases.

App Store Server API introduces the JWSTransactionDecodedPayload object, which includes richer data like price, currency, promotional offer details, and transaction type, unavailable in older methods.

Migration Considerations

Migrating to StoreKit 2 while using verifyReceipt is not recommended due to synchronization delays between the two systems.

  • A practical approach is to transition to the App Store Server API first, enabling accurate transaction handling, and then move to StoreKit 2.
  • Migrating to both systems simultaneously is also viable if resources allow.

By fully leveraging the capabilities of StoreKit 2 and the App Store Server API, you can not only enhance your app's transaction management but also provide a seamless user experience. Strategic migration planning is key to maximizing the benefits of these tools and if you need any help along the way, contact our team or me directly, I'll be happy to help!

Suren

Suren

SDK Engineer at Qonversion

Suren develops SDKs that help developers integrate Qonversion seamlessly.

Share:

Ready to optimize your subscriptions?

Start using Qonversion today and see the difference.