Validating Receipts Locally

When an application is installed from the App Store, it contains an application receipt that is cryptographically signed, ensuring that only Apple can create valid receipts. The receipt is stored inside the application bundle.  you can retrieve recipe using the code snippet bellow.

Locate and Retrieve Receipt

ISN_Security.OnReceiptLoaded += HandleOnReceiptLoaded;

void HandleOnReceiptLoaded (ISN_LocalReceiptResult res) {
	ISN_Security.OnReceiptLoaded -= HandleOnReceiptLoaded;
If the recipe is null use you can try to refresh the receipt.  
ISN_Security.OnReceiptRefreshComplete += HandleOnReceiptRefreshComplete;

void HandleOnReceiptRefreshComplete (ISN_Result res) {
Do not try to terminate the app. At your option, you may give the user a grace period or restrict functionality inside your app.

Here are some key points about receipts:

  • A receipt is created and signed by Apple through the App Store.
  • A receipt is issued for a specific version of an application and a specific device.
  • A receipt is stored locally on the device.
  • A receipt is issued each time an installation or an update occurs.
    • When an application is installed, a receipt that matches the application and the device is issued.
    • When an application is updated, a receipt that matches the new version of the application is issued.
  • A receipt is issued each time a transaction occurs:
    • When an in-app purchase occurs, a receipt is issued so that it can be accessed to verify that purchase.
    • When previous transactions are restored, a receipt is issued so that it can be accessed to verify those purchases.

After receipt is retrieved you can validate the recipe. 

Warning: IOS Native plugin does not provide validation functionality, since is will required OpenSSL lib to be included in the plugin. So you should create validation code on client side using OpenSSL lib, or as more sequre option, send recipe data to the server and validate it on server side.

For client side validation you will need to get device GUID. See the GUID retrieving example at the code snippet bellow.

Known Issues

if you have not receipt on a device you can try to refresh it with StartReceiptRefreshRequest method as described above. In some cases you may have refresh erro with code similar to:

The operation couldn’t be completed. (SSErrorDomain error 100.)

This may happend if you’re trying to test iOS App Store receipt validation and after you fiund no receipt on a devise you a trying to refresh ot. you are almost certainly going to come across the mysterious and enigmatic SSErrorDomain Error 100

As far as I can tell, code 100 is the App Store’s way of telling you “Sorry, I have no receipt for that bundle ID for that user”. That’s unlikely to happen in production unless shenanigans are underway (a receipt is generated even for a free app ‘Get’), but it can happen often in development. The sandbox App Store appears to have the ability to generate fake receipts when requested, but all ducks need to be in a row for this to happen.

In the sandbox (Development/Ad Hoc builds):

  • If you don’t have an app record set up in iTunes Connect, you’ll get a Code 100
  • If you’re signed in with your regular Apple ID instead of a sandbox account: Code 100
  • If you’re signed in with a sandbox account associated with a different iTunes Connect account: Code 100

The story is a bit different for Apple Testflight builds – these are production builds with special handling for in-app purchases, and the App Store (currently) does NOT generate a fake original purchase receipt. I haven’t tested this myself, but from a developer report on the dev forums (login required):

  • If you have a virgin install from TestFlight, you’ll get a Code 100
  • If you’ve previously installed the App Store version of the app, you’ll get a receipt
  • If you have a virgin install from TestFlight but have made an in-app purchase, you’ll get a receipt

Hopefully, this saves others some frustration. (Source)

Validating Receipts With the App Store

Apple’s official recommendation to perform receipt validation is to connect to your own server, which then connects to Apple’s servers to validate the receipts.For a number of reasons, this is more secure than connecting to Apple directly. So ideally, you should send loaded receipt data to your own server, and validate it. Then return the response to your app with a necessary info.

But as example, let's just run receipt validation inside the unity app once receipt is loaded. See the code snippet bellow:

ISN_Security.OnReceiptLoaded += HandleOnReceiptLoaded;

byte[] ReceiptData = null;
void HandleOnReceiptLoaded (ISN_LocalReceiptResult res) {
	ISN_Security.OnReceiptLoaded -= HandleOnReceiptLoaded;

	if(result.Receipt != null) {
		ReceiptData = result.Receipt;

private IEnumerator SendRequest() {

	string base64string = System.Convert.ToBase64String(ReceiptData);

	Dictionary<string, object> OriginalJSON =  new Dictionary<string, object>();
	OriginalJSON.Add("receipt-data", base64string);
	//Only used for receipts that contain auto-renewable subscriptions. Your app’s shared secret (a hexadecimal string).
	//OriginalJSON.Add("password", "");

	string data = ISN_MiniJSON.Json.Serialize(OriginalJSON);
	byte[] binaryData = System.Text.ASCIIEncoding.UTF8.GetBytes(data);

	//Should be used with live enviroment
	//WWW www = new WWW("", binaryData);

	//Should be used with the sandbox enviroment
	WWW www = new WWW("", binaryData);
	yield return www;

	if(www.error == null) { 
	} else {

When request is completed, you can response with status error, similar to:


In this case following information should be useful for you:

Status Code



The App Store could not read the JSON object you provided.


The data in the receipt-data property was malformed or missing.


The receipt could not be authenticated.


The shared secret you provided does not match the shared secret on file for your account.

Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.


The receipt server is not currently available.


This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response.

Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.


This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.


This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead.

If everything is done right, and you receipt is valid you should get JSON data similar to posted bellow:

{  "status":0,
      "receipt_creation_date":"2016-05-25 21:10:25 Etc/GMT",
      "receipt_creation_date_pst":"2016-05-25 14:10:25 America/Los_Angeles",
      "request_date":"2016-05-25 21:44:46 Etc/GMT",
      "request_date_pst":"2016-05-25 14:44:46 America/Los_Angeles",
      "original_purchase_date":"2013-08-01 07:00:00 Etc/GMT",
      "original_purchase_date_pst":"2013-08-01 00:00:00 America/Los_Angeles",
            "purchase_date":"2016-05-25 21:10:25 Etc/GMT",
            "purchase_date_pst":"2016-05-25 14:10:25 America/Los_Angeles",
            "original_purchase_date":"2016-05-25 21:10:25 Etc/GMT",
            "original_purchase_date_pst":"2016-05-25 14:10:25 America/Los_Angeles",

You may read more about receipts validation at Apple Official Documentation Website.

You will find full use example inside the MarketExample.cs script.

Receipt Structure

Let’s take a technical look at the receipt file. Its structure looks like this:

A receipt file consist of a signed PKCS #7 container that embeds a DER-encodedASN.1 payload, a certificate chain, and a digital signature.

  • The payload is a set of attributes that contains the receipt information; each attribute contains a type, a version, and a value. Among the attribute values, you find the bundle identifier and the bundle version for which the receipt was issued.
  • The certificate chain is the set of certificates required to properly verify the signature digest — the leaf certificate is the certificate that has been used to sign the payload.
  • The signature is the encrypted digest of the payload. By checking this digest, you can verify that the payload has not been tampered with.


The Container

The receipt container is a PKCS #7 envelope, which is signed by Apple with a dedicated certificate. The container’s signature guarantees the authenticity and the integrity of the encapsulated payload.

To verify the signature, two checks are needed:

  • The certificate chain is validated against the Apple Certificate Authority Root certificate — this is the authenticity check.
  • A signature is computed using the certificate chain and compared to the one found in the container — this is the integrity check.

The Payload

The ASN.1 payload is defined by the following structure:

ReceiptModule DEFINITIONS ::=

ReceiptAttribute ::= SEQUENCE {
    type    INTEGER,
    version INTEGER,
    value   OCTET STRING

Payload ::= SET OF ReceiptAttribute


A receipt attribute has three fields:

  • The type field identifies each attribute by its type. Apple has published a list of public attributes that can be used to extract information from the receipt. You may also find unlisted attributes while parsing a receipt, but it’s best to simply ignore them (mostly because they are reserved by Apple for future use).
  • The version field is not used for now.
  • The value field contains the data as an array of bytes (even if its name may suggest it, this is not a string).

The payload is encoded using DER (Distinguished Encoding Rules). This kind of encoding provides an unequivocal and compact result for ASN.1 structures. DER uses a pattern of type-length-value triplets and byte constants for each type tag.

To better illustrate the concept, here are some concrete examples of DER-encoded content applied to a receipt. The figure below shows how a receipt module is encoded:

  • The first byte identifies an ASN.1 set.
  • The three following bytes encode the length of the set’s content.
  • The contents of the set are the receipt attributes.

The next figure shows how a receipt’s attributes are encoded:

  • The first byte identifies an ASN.1 sequence.
  • The second byte encodes the length of the sequence’s content.
  • The content of the sequence is:
    • The attribute’s type encoded as an ASN.1 INTEGER (the first byte identifies an ASN.1 INTEGER, the second byte encodes its length, and the third byte contains the value).
    • The attribute’s version encoded as an ASN.1 INTEGER (the first byte identifies an ASN.1 INTEGER, the second byte encodes its length, and the third byte contains the value).
    • The attribute’s value encoded as an ASN.1 OCTET-STRING (the first byte identifies an ASN.1 OCTET-STRING, the second byte encodes its length, and the remaining bytes contain the data).

By using an ASN.1 OCTET-STRING for the attribute’s value, it is very easy to embed various values like UTF-8 strings, ASCII strings, or numbers. The attribute’s value can also contain a receipt module in the case of in-app purchases. Some examples are shown in the figures below:

Validating the Receipt

The steps to validate a receipt are as follows:

  • Locate the receipt. If no receipt is found, then the validation fails.
  • Verify the receipt authenticity and integrity. The receipt must be properly signed by Apple and must not be tampered with.
  • Parse the receipt to extract attributes such as the bundle identifier, the bundle version, etc.
  • Verify that the bundle identifier found inside the receipt matches the bundle identifier of the application. Do the same for the bundle version.
  • Compute the hash of the GUID of the device. The computed hash is based on device-specific information.
  • Check the expiration date of the receipt if the Volume Purchase Program is used.


You can read more about receipt validation at Apple Developer Guide, ot this article.