Integrate, analyze and improve subscriptions in your iOS app

Dev notes

App Store receipt validation. How to implement?

There are still so many questions in StackOverflow regarding App Store receipts validation, so we decided to write an article about this. Here are common questions developers have to deal with.

What is App Store receipt?

It’s a signed file in a PKCS#7 format, which contains information about all in-app purchases in the app. It’s located in the app bundle and can be easily read by calling Bundle.main.appStoreReceiptURL.

Does a receipt always exist in the app?

If a user downloaded the app from the App Store – yes. However, in sandbox if your app was installed via Xcode or Testflight, then there won’t be a receipt until you make a purchase or restore.

What does “Receipt validation” mean?

It basically means to decrypt encrypted receipt file, get JSON data and verify purchases.

When do I need to validate a receipt?

  • In case of auto-renewable subscriptions when a purchase has just been made – to get an expiration date.
  • When restoring in-app purchases. If user has reinstalled the app or launched it from a new device, you must provide a mechanism to restore his purchases and give access to features he already paid for. 
  • A couple of years ago, when jailbreak was commonly used, developers used to validate receipts to verify that payment wasn’t hacked – I believe those days are gone now, and it’s not so necessary anymore.

Which in-app purchases can I restore via receipt validation?

There are four types of in-app purchases: consumables, non-consumables, non-renewing subscriptions and auto-renewable subscriptions. You can restore all except consumables.

Consumable purchases are, for example, coins in your app – something that can be purchased many times. There is no way to restore the number of your coins, you should keep it on your server.

What are the validation methods?

You could choose between three validation approaches: 

  • local validation using OpenSSL,
  • online validation through Apple directly from your iOS device,
  • online validation through Apple using your server.

Which validation method is better?

Well, local validation is too complicated and it’s hard to implement it. Besides that, you have to add OpenSSL library into your Xcode project. And in some cases you will need to refresh the receipt.

Apple doesn’t recommend Online validation because of security reasons: HTTPS request can be intercepted via man-in-the-middle attack.

It’s better to validate receipts using your own server. Especially, Apple adds new fields to the receipt, like grace_period_expires_date or subscription_group_identifier. You can apply changes on your server without app update. Besides that, user can easily hack those two methods just by changing system date on device.

What is Shared Secret?

It’s a special string key, which a developer uses to validate receipts with auto-renewable subscriptions. You will put Shared Secret into HTTPS request parameters.

Where can I get Shared Secret?

Go to App Store Connect, open your app, then go to “Functions” and in “In-App Purchases” tab you will see “App-Specific Shared Secret” button. Generate a new key, if it doesn’t exist.

Receipt validation example code

func validateReceipt(){
        #if DEBUG
                   let urlString = "https://sandbox.itunes.apple.com/verifyReceipt"
               #else 
                   let urlString = "https://buy.itunes.apple.com/verifyReceipt"
               #endif
        
        guard let receiptURL = Bundle.main.appStoreReceiptURL, let receiptString = try? Data(contentsOf: receiptURL).base64EncodedString() , let url = URL(string: urlString) else {
                return
        }
        
        let requestData : [String : Any] = ["receipt-data" : receiptString, 
                                            "password" : "YOUR_SHARED_SECRET", 
                                            "exclude-old-transactions" : false]
        let httpBody = try? JSONSerialization.data(withJSONObject: requestData, options: [])
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("Application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = httpBody
        URLSession.shared.dataTask(with: request)  { (data, response, error) in
            // convert data to Dictionary and view purchases
        }.resume()        
    }

This is a Swift example of validating receipt from iOS device. Don’t forget to change YOUR_SHARED_SECRET with your own shared secret.

After you get data, convert it into Dictionary:

DispatchQueue.main.async {
	if let data = data, let jsonData = try? JSONSerialization.jsonObject(with: data, options: .allowFragments){
    // your non-consumable and non-renewing subscription receipts are in `in_app` array
    // your auto-renewable subscription receipts are in `latest_receipt_info` array
  }                
}

Example of decrypted JSON App Store receipt 

Here you can view an example of JSON receipt with two transactions for auto-renewable subscription.

What’s the difference between in_app and latest_receipt_info?

  • latest_receipt_info contains all transactions including subscription renewals.
  • in_app contains transactions for non-consumables and non-renewing subscriptions. There will also appear only first receipt for your auto-renewable subscription. Consumables will also appear in this array but will disappear after developer finishes transaction.

Conclusion

In Apphud we implemented App Store receipt validation for apps with auto-renewable subscriptions in our open-source SDK. Besides that, Apphud helps to track subscriptions, analyze key metrics, grow your revenue by reducing voluntary and involuntary churn, etc. You can start using Apphud free.

Want to discuss the article? Join our Slack community (in English 🇺🇸🇬🇧) and Telegram chat (in Russian 🇷🇺)

Subscribe to our newsletter!