One of the more tricky parts of adding in-app purchases to your Apple App is verifying receipts on a server. Apple strongly recommends that you use a server to parse and validate receipts. This can helps to reduce the risk of fraud. But how do you handle things like the call to verifyReceipt or SKReceiptRefreshRequest? We’ll break this all down by showing you how to create a basic server to do iOS receipt validation.
Apple’s StoreKit framework provides a mechanism for selling in-app purchases or subscriptions through the App Store.
An essential artifact, the App Store receipt, is used to verify purchases and understand purchase activity. In this multi-part series, we will go beyond Apple’s documentation to demystify the receipt by coding a simple Python receipt validation script then progressively building out a server-side receipt validation app using Python, Flask, and Docker. This will make it easy for you to modify and deploy to a cloud service like AWS, GCP, or Azure.
Before we jump into the Python, let’s quickly talk about how to access the receipt on the client-side.
The primary way you will access an App Store receipt is from your app code.
From your Xcode project, you can use the Bundle.main.appStoreReceiptURL to access the Base 64 encoded receipt data.
Here’s an example:
The code above is not guaranteed to return a receipt. Whether a receipt is returned depends on the app build:
Now that you know how to retrieve the encoded receipt, next we’ll talk about receipt validation.
Now that you know how to retrieve the encoded receipt, next you need to validate it.
It’s possible to validate a receipt from the client-side or the server-side. Server-side receipt validation is more complicated, but the benefits are numerous. Especially if you offer auto-renewing subscriptions, server-side validation is strongly encouraged. You and read more about choosing a receipt validation technique from Apple’s documentation.
An App Store receipt provides a record of the sale of an app or any purchase made from within the app, and you can authenticate purchased content by adding receipt validation code to your app or server. - Apple Developer Documentation
For this series, we’re going to employ server-side receipt validation. To do that, we're going to lean on Python, Flask, and Docker to consume an encoded receipt passed up from your app’s client code. Then, we’ll dig deeper into interpreting the decoded receipt, as well as what response to send back to your client.
If you’re ready to head straight into the details of a decoded receipt, jump to our definitive guide, an element-by-element breakdown of a decoded receipt.
First, let’s build a rudimentary script to to better understand the receipt verification workflow.
Apple provides a verifyReceipt service to be used for server-side receipt validation. The basic request and response pattern for this service is pretty straightforward, which we can demonstrate with a simple Python CLI script that does the following:
Let’s get started!
We will be using Python 3 with standard libraries, so the first thing we need to do is import the modules we will need.
Next, we need create a global variables for the verifyReceipt endpoint. There are actually endpoints: Sandbox (sandbox.itunes.apple.com) & Production (buy.itunes.apple.com). Our script will support receipts from both environments, so let’s set global variables defining each endpoint.
To determine which endpoint needs to be used, you need to know what kind of app build was used to make the purchase.
Since this script will take in command-line arguments, let’s create a simple method to handle sending the verify receipt request to Apple. Our method will accept several arguments including whether or not we should use the Sandbox endpoint.
Best Practice: Apple recommends first sending a receipt to Production. If the receipt is for Sandbox, the response will contain a status field with the value 21007. This is your signal to try the Sandbox endpoint instead.
Next, we need to construct a valid requestBody which consists of a JSON data structure contains the Base 64 encoded receipt and a password field which is required for auto-renewable subscriptions. To locate your app’s hexadecimal shared secret via App Store Connect, check out this guide.
Now we are ready to send the HTTP POST request to Apple.
If the request was successful, you will receive a HTTP 200 OK response code. This means we can expect to receive a JSON responseBody. The first thing we need to inspect from the responseBody is the status field. If status is 0, the receipt is valid and many other fields will be present in the responseBody as well. We’ll dive deeper into the various elements of the receipt later in this series.
For now, we will use the status value to print a message explaining whether the receipt was validated or not. Just in case we don’t receive that HTTP 200 OK we were expecting, we’ll also catch and print any unexpected HTTP response codes here as well. In production, you can expect to see non-200 responses from Apple so you will need to add logic to handle this and retry if need be.
Here are the most common status values you will encounter:
There are others which are much more rare that you can read about here.
Now we’re ready to prepare our command-line arguments. We expect a file containing an encoded receipt to be passed to this script. If we don’t at least see one command-line argument, let’s print a helpful message.
Let’s try to read in the encoded receipt data from the file path provided in that first argument.
We need to see if any optional command-line arguments were provided. This code supports a --secret argument to pass in the hexadecimal shared secret discussed previously. Additionally, --use_sandbox tells the script to use the Sandbox verifyReceipt endpoint. Otherwise, it will default to Production.
Finally, we construct our verify_receipt method call.
You now should have a good sense for what’s involved to send a receipt validation request to Apple and the basic response codes you can expect to encounter. Head on over to GitHub for the complete source code and examples for the Python CLI covered in this tutorial.
In the next part of this series, we dig deeper into the receipt responseBody.
Until next time, happy validating!
Learn the difference between an iOS Unit Test and UI Test and when to use each. Discover how to move a Unit Test into a UI Test plus some helpful tips to deal how unit tests and UI Tests are run.
🍏 🤖 iOS or Android? 3 variables to consider when choosing whether to first develop your mobile app for iOS or Android platforms.