/

/

The Ultimate Handboo...

The Ultimate Handbook On App-Store Receipt Validation

Michael Qonversion

Michael

Apr 27, 2021

iOS developers are familiar with StoreKit, a framework that allows them to monetize apps through in-app purchases and subscriptions. For everything to work smoothly, avoid piracy, and make sure users get access to what they purchased developers need to perform receipt validation.

The process begins once a user makes an in-app purchase. After that, developers have to process the App Store receipt, the data of which is Base64-encoded. For a decoded version, you need to pass it through Apple’s verifyReceipt endpoint.

Unfortunately, understanding the decrypted receipt can be an arduous process. There are many nuances and potential pitfalls in the data structure. That’s why, in this article, we will cover each element of the decrypted receipt and help you get a better grasp on how you can avoid common problems. 

The Ultimate Handbook On App-Store

Elements of a Decrypted Receipt

responseBody.Receipt

Now that we’ve covered all the responseBody properties, let’s deep dive into the potential elements of responseBody.Receipt.

This may look overwhelming, but don’t worry. We are going to look over each of these properties in more detail so you can get a better understanding of what they mean.

adam_id and app_item_id

This property is generated by App Store Connect and used to uniquely identify the app corresponding with the receipt. It’s a 64-bit long integer that is assigned solely in production. Thus, expect a unique value in production and a 0 in the sandbox.

application_version

Indicates the app’s version number at the time of the receipt’s receipt_creation_date_ms. It corresponds to CFBundleVersion (iOS) or CFBundleShortVersionString (macOS) in the Info.plist. The value is always “1.0” in the sandbox.

bundle_id

This string is provided on App Store Connect and relates to the value of CFBundleIdentifier in the Info.plist file of the app. It’s a bundle identifier to the app to which the receipt belongs.

download_id

This is a unique identifier for the app download transaction. In the sandbox, the field will be populated with a 0, while in production you will see a unique identifier.

Unfortunately, the download_id is not well covered by Apple. It appears to be connected to the download transaction represented by original_application_version and original_purchase_date.

expiration_date, expiration_date_pst, expiration_date_ms

Illustrate the time at which the receipt expires for apps bought through the Volume Purchase Program. 

For the expiration_date the date-time format is similar to that of ISO 8601. For the expiration_date_pst it’s in the Pacific Time zone. Finally, for expiration_date_ms it’s in UNIX epoch time format, in milliseconds.

in_app

This is an array that contains in-app purchase receipt fields for all in-app purchase transactions.

original_application_version

Indicates the version of the app that the user originally acquired. If the originally purchased version was 2.5, the value in this field would be 2.5, even if currently the application runs on version 5.0. In the sandbox, the value is always “1.0”.

This number corresponds to CFBundleVersion (iOS) or CFBundleShortVersionString (macOS) from the Info.plist.

Original_purchase_date, original_purchase_date_pst, original_purchase_date_ms

Illustrate the time of the original app purchase. 

As was the case with the expiration_date property, the original_purchase_date has the date-time format similar to that of ISO 8601. original_purchase_date_pst is in the Pacific Time zone format. original_purchase_date_ms is in UNIX epoch time format, in milliseconds.

preorder_date, preorder_date_pst, preorder_date_ms

If the app was available for pre-orders, this field would indicate the time the user made the pre-order. 

preorder_date property has a date-time format similar to that of ISO 8601. preorder_date_pst is in the Pacific Time zone format and preorder_date_ms is in UNIX epoch time format, in milliseconds. 

receipt_creation_date, receipt_creation_date_pst, receipt_creation_date_ms

Specify the time at which the App Store generated the receipt.

You probably already got the idea, but let us detail the formats anyway. receipt_creation_date property has a date-time format similar to that of ISO 8601. Receipt_creation_date_pst is in the Pacific Time zone format and receipt_creation_date_ms is in UNIX epoch time format, in milliseconds.

receipt_type

Lists the type of the receipt that was generated. The value relates to the environment in which the app or VPP purchase was made. Possible values include:

  • Production: the receipt was created in the App Store production environment

  • ProductionVPP: the receipt was created in the VPP production environment

  • ProductionSandbox: the receipt was created in the App Store sandbox environment

  • Production VPPSandbox: the receipt was created in the VPP sandbox environment

request_date, request_date_pst, request_date_ms

Show the time the request to the verifyReceipt endpoint was processed and generated a response. 

For the request_date the date-time format is similar to that of ISO 8601. For the request_date_pst it’s in the Pacific Time zone. Finally, for request_date_ms it’s in UNIX epoch time format, in milliseconds.

version_external_identifier

A random number that specifies a revision of the app. In the sandbox, this value is “0”.

responseBody.latest_receipt_info Properties

Time to move on to the properties of responseBody.latest_receipt_info

Hopefully, you already got used to our format. Just like before, let’s explore each of these keys individually.

cancellation_date, cancellation_date_pst, cancellation_date_ms

Indicate the time at which the subscription was canceled. A couple of things can cause this. First, Apple’s customer support team may have refunded the transaction. Second, if Family Sharing is set up, changes to access could lead to subscription cancellation. Finally, the user may have simply upgraded to a different product.

For the cancellation_date the date-time format is similar to that of ISO 8601. For the cancellation_date_pst it’s in the Pacific Time zone. Lastly, for cancellation_date_ms it’s in UNIX epoch time format, in milliseconds.

cancellation_reason

Lists the reason behind the refunded transaction. Possible values are 1 and 0. “1” means that the customer canceled because of an issue within your app. On the other hand, “0” means the cancellation occurred for another reason. Often, if the customer accidentally made the purchase.

expires_date, expires_date_pst, expires_date_ms

Illustrate the time at which the subscription will expire or renew.

Expires_date property has a date-time format similar to that of ISO 8601. expires_date_pst is in the Pacific Time zone format and expires_date_ms is in UNIX epoch time format, in milliseconds.

in_app_ownership_type

In this property, you will see whether the user is the purchaser of the product, or rather a family member with access to it through Family Sharing.

The value will either be FAMILY_SHARED or PURCHASED.

is_in_intro_offer_period

Illustrates if an auto-renewable subscription is in the introductory price period or not. Possible values are true and false. true indicates that the subscription is in fact in an introductory price period, while false negates it. 

is_trial_period

Specifies whether a subscription is in the free trial period or not.

is_upgraded

This property is only present for upgraded transactions and illustrates if a subscription has been canceled because of an upgrade. The value can only be true, signifying that this is what happened.

offer_code_ref_name

Only present when a subscription offer code was redeemed. Indicates the reference name of the offer that was set up in App Store Connect.

original_purchase_date, original_purchase_date_pst, original_purchase_date_ms

Illustrate the time of the original auto-renewable subscription purchase.

original_purchase_date property has a date-time format similar to that of ISO 8601. original_purchase_date_pst is in the Pacific Time zone format and original_purchase_date_ms is in UNIX epoch time format, in milliseconds.

original_transaction_id

Identifies the original purchase transaction.

product_id

Identifies the product purchased as set up for that product in App Store Connect.

promotional_offer_id

Identifies the user’s redeemed subscription offer.

purchase_date, purchase_date_pst, purchase_date_ms

Showcase the time at which the user’s account was charged by the App Store for a subscription purchase or renewal.

Purchase_date property has a date-time format similar to that of ISO 8601. purchase_date_pst is in the Pacific Time zone format and purchase_date_ms is in UNIX epoch time format, in milliseconds.

quantity

Illustrates the number of accessible products purchased. Usually 1-10, typically 1. 

subscription_group_identifier

Identifies the group to which the subscription belongs.

web_order_line_item_id

This is the primary key for identifying subscription purchases. It identifies purchase occurrences across devices.

transaction_id

Identifies purchases, restores, and renewals. The transaction is a purchase when transaction_id matches original_transaction_id. If they don’t match then it’s a restore or renewal.

responseBody.pending_renewal_info Properties

Now, let’s take a look at responseBody.pending_renewal_info properties!

} ], "latest_receipt": "[new]", "pending_renewal_info": [ { "expiration_intent": "1", "auto_renew_product_id": "test.vip.6month.3d.3999.1", "is_in_billing_retry_period": "0", "product_id": "product.name.1", "original_transaction_id": "100000000000000", "auto_renew_status": "0" } ], "status": 0 } Copy

auto_renew_product_id

Identifies customer’s unresolved subscription renewal. You will only see this field if the user has downgraded or crossgraded to a different duration subscription for the following period. 

auto_renew_status

Indicates whether an auto-renewable subscription will renew at the end of the current subscription period. “1” illustrates that it will and “0” shows that the customer has canceled automatic renewal.

If you see the latter, the customer is at high risk of churn and it might be worth presenting him with a special offer.

expiration_intent

Showcases the reason for subscription expiry. Possible values are 1-5.

  • 1 = the customer canceled the subscription themselves. Consider sending a win-back campaign.

  • 2 = there was a billing error. Consider sending a reminder to update billing information to avoid losing access. 

  • 3 = the user didn’t agree to a price increase.

  • 4 = the product was unavailable at the time of renewal.

  • 5 = unknown

grace_period_expires_date, grace_period_expires_date_pst, grace_period_expires_date_ms

Signify the time when the subscription renewal grace period expires.

grace_period_expires_date property has a date-time format similar to that of ISO 8601. grace_period_expires_date_pst is in the Pacific Time zone format and Grace_period_expires_date_ms is in UNIX epoch time format, in milliseconds.

is_in_billing_retry_period

Illustrates if the customer’s auto-renewable subscription is in the billing retry period. Possible values are 1 and 0. The former indicates that the App Store is attempting to renew the subscription while the latter shows it has stopped attempting to renew. If the value is 1, this is a good time to send a billing update request. 

offer_code_ref_name

Showcases the reference name of the subscription offer you set up in App Store Connect. This field is only visible when a subscription offer code was redeemed.

original_transaction_id

Identifies the transaction of the original purchase.

price_consent_status

Indicates the consent status for the subscription price increase. The customer has either consented to the increase or hasn’t. 1 means that the customer has agreed and 0 illustrates that the customer has been notified of the price increase but hasn’t agreed to it yet.

product_id

Identifies that purchased product per your setup in App Store Connect.

Date Format Variations

You have probably noticed that throughout the decrypted receipt there are various date formats:

  • ISO 8601 -like GMT

  • ISO 8601 -like PST

  • UNIX epoch time in milliseconds

But do you already know what these date-time formats mean?

In its documentation, Apple refers to a data-time format similar to the ISO 8601 or an RFC 3339 date. Converting these string-based formats can be difficult, so let’s take a closer look.

As you have seen throughout this document, each base date has two modifiers: _pst and _ms. The former represents ISO 8601-like PST (Pacific Standard Time) and the latter represents milliseconds since UNIX epoch time.

A couple of things you should note:

  • Be extra careful during Pacific Daylight Time (PDT) because the value remains in PST time.

  • The easiest format to work with is UNIX epoch time in milliseconds because the majority of modern programming languages can convert it to a native date-time format.

Improve Your Receipt Management

Congratulations! You got through our rundown of decrypted receipt elements. 

Hopefully, you now have a better grasp on each of them, but don’t worry if things are still a bit overwhelming. You can always bookmark this page for future reference. 

Overall, you’ve probably noticed that decrypted receipts are quite complex. New features are added to the App Store every year and writing good code to manage receipts and leverage everything the Store has to offer is incredibly challenging.

We understand that you may not want to do all of this yourself. That’s precisely why we are here! 

If you want to avoid the hassle with StoreKit or don’t want to build your own subscription validation server, you can always turn to Qonversion! You can head to our Apple Receipt Checker right now or explore the Product Center which provides full in-app purchases infrastructure. As developers, you don’t have to perform this time-consuming receipts management anymore. Leave it to us.

The Ultimate Handbook On App-Store Receipt Validation

Michael Qonversion

Michael

Apr 27, 2021

iOS developers are familiar with StoreKit, a framework that allows them to monetize apps through in-app purchases and subscriptions. For everything to work smoothly, avoid piracy, and make sure users get access to what they purchased developers need to perform receipt validation.

The process begins once a user makes an in-app purchase. After that, developers have to process the App Store receipt, the data of which is Base64-encoded. For a decoded version, you need to pass it through Apple’s verifyReceipt endpoint.

Unfortunately, understanding the decrypted receipt can be an arduous process. There are many nuances and potential pitfalls in the data structure. That’s why, in this article, we will cover each element of the decrypted receipt and help you get a better grasp on how you can avoid common problems. 

The Ultimate Handbook On App-Store

Elements of a Decrypted Receipt

responseBody.Receipt

Now that we’ve covered all the responseBody properties, let’s deep dive into the potential elements of responseBody.Receipt.

This may look overwhelming, but don’t worry. We are going to look over each of these properties in more detail so you can get a better understanding of what they mean.

adam_id and app_item_id

This property is generated by App Store Connect and used to uniquely identify the app corresponding with the receipt. It’s a 64-bit long integer that is assigned solely in production. Thus, expect a unique value in production and a 0 in the sandbox.

application_version

Indicates the app’s version number at the time of the receipt’s receipt_creation_date_ms. It corresponds to CFBundleVersion (iOS) or CFBundleShortVersionString (macOS) in the Info.plist. The value is always “1.0” in the sandbox.

bundle_id

This string is provided on App Store Connect and relates to the value of CFBundleIdentifier in the Info.plist file of the app. It’s a bundle identifier to the app to which the receipt belongs.

download_id

This is a unique identifier for the app download transaction. In the sandbox, the field will be populated with a 0, while in production you will see a unique identifier.

Unfortunately, the download_id is not well covered by Apple. It appears to be connected to the download transaction represented by original_application_version and original_purchase_date.

expiration_date, expiration_date_pst, expiration_date_ms

Illustrate the time at which the receipt expires for apps bought through the Volume Purchase Program. 

For the expiration_date the date-time format is similar to that of ISO 8601. For the expiration_date_pst it’s in the Pacific Time zone. Finally, for expiration_date_ms it’s in UNIX epoch time format, in milliseconds.

in_app

This is an array that contains in-app purchase receipt fields for all in-app purchase transactions.

original_application_version

Indicates the version of the app that the user originally acquired. If the originally purchased version was 2.5, the value in this field would be 2.5, even if currently the application runs on version 5.0. In the sandbox, the value is always “1.0”.

This number corresponds to CFBundleVersion (iOS) or CFBundleShortVersionString (macOS) from the Info.plist.

Original_purchase_date, original_purchase_date_pst, original_purchase_date_ms

Illustrate the time of the original app purchase. 

As was the case with the expiration_date property, the original_purchase_date has the date-time format similar to that of ISO 8601. original_purchase_date_pst is in the Pacific Time zone format. original_purchase_date_ms is in UNIX epoch time format, in milliseconds.

preorder_date, preorder_date_pst, preorder_date_ms

If the app was available for pre-orders, this field would indicate the time the user made the pre-order. 

preorder_date property has a date-time format similar to that of ISO 8601. preorder_date_pst is in the Pacific Time zone format and preorder_date_ms is in UNIX epoch time format, in milliseconds. 

receipt_creation_date, receipt_creation_date_pst, receipt_creation_date_ms

Specify the time at which the App Store generated the receipt.

You probably already got the idea, but let us detail the formats anyway. receipt_creation_date property has a date-time format similar to that of ISO 8601. Receipt_creation_date_pst is in the Pacific Time zone format and receipt_creation_date_ms is in UNIX epoch time format, in milliseconds.

receipt_type

Lists the type of the receipt that was generated. The value relates to the environment in which the app or VPP purchase was made. Possible values include:

  • Production: the receipt was created in the App Store production environment

  • ProductionVPP: the receipt was created in the VPP production environment

  • ProductionSandbox: the receipt was created in the App Store sandbox environment

  • Production VPPSandbox: the receipt was created in the VPP sandbox environment

request_date, request_date_pst, request_date_ms

Show the time the request to the verifyReceipt endpoint was processed and generated a response. 

For the request_date the date-time format is similar to that of ISO 8601. For the request_date_pst it’s in the Pacific Time zone. Finally, for request_date_ms it’s in UNIX epoch time format, in milliseconds.

version_external_identifier

A random number that specifies a revision of the app. In the sandbox, this value is “0”.

responseBody.latest_receipt_info Properties

Time to move on to the properties of responseBody.latest_receipt_info

Hopefully, you already got used to our format. Just like before, let’s explore each of these keys individually.

cancellation_date, cancellation_date_pst, cancellation_date_ms

Indicate the time at which the subscription was canceled. A couple of things can cause this. First, Apple’s customer support team may have refunded the transaction. Second, if Family Sharing is set up, changes to access could lead to subscription cancellation. Finally, the user may have simply upgraded to a different product.

For the cancellation_date the date-time format is similar to that of ISO 8601. For the cancellation_date_pst it’s in the Pacific Time zone. Lastly, for cancellation_date_ms it’s in UNIX epoch time format, in milliseconds.

cancellation_reason

Lists the reason behind the refunded transaction. Possible values are 1 and 0. “1” means that the customer canceled because of an issue within your app. On the other hand, “0” means the cancellation occurred for another reason. Often, if the customer accidentally made the purchase.

expires_date, expires_date_pst, expires_date_ms

Illustrate the time at which the subscription will expire or renew.

Expires_date property has a date-time format similar to that of ISO 8601. expires_date_pst is in the Pacific Time zone format and expires_date_ms is in UNIX epoch time format, in milliseconds.

in_app_ownership_type

In this property, you will see whether the user is the purchaser of the product, or rather a family member with access to it through Family Sharing.

The value will either be FAMILY_SHARED or PURCHASED.

is_in_intro_offer_period

Illustrates if an auto-renewable subscription is in the introductory price period or not. Possible values are true and false. true indicates that the subscription is in fact in an introductory price period, while false negates it. 

is_trial_period

Specifies whether a subscription is in the free trial period or not.

is_upgraded

This property is only present for upgraded transactions and illustrates if a subscription has been canceled because of an upgrade. The value can only be true, signifying that this is what happened.

offer_code_ref_name

Only present when a subscription offer code was redeemed. Indicates the reference name of the offer that was set up in App Store Connect.

original_purchase_date, original_purchase_date_pst, original_purchase_date_ms

Illustrate the time of the original auto-renewable subscription purchase.

original_purchase_date property has a date-time format similar to that of ISO 8601. original_purchase_date_pst is in the Pacific Time zone format and original_purchase_date_ms is in UNIX epoch time format, in milliseconds.

original_transaction_id

Identifies the original purchase transaction.

product_id

Identifies the product purchased as set up for that product in App Store Connect.

promotional_offer_id

Identifies the user’s redeemed subscription offer.

purchase_date, purchase_date_pst, purchase_date_ms

Showcase the time at which the user’s account was charged by the App Store for a subscription purchase or renewal.

Purchase_date property has a date-time format similar to that of ISO 8601. purchase_date_pst is in the Pacific Time zone format and purchase_date_ms is in UNIX epoch time format, in milliseconds.

quantity

Illustrates the number of accessible products purchased. Usually 1-10, typically 1. 

subscription_group_identifier

Identifies the group to which the subscription belongs.

web_order_line_item_id

This is the primary key for identifying subscription purchases. It identifies purchase occurrences across devices.

transaction_id

Identifies purchases, restores, and renewals. The transaction is a purchase when transaction_id matches original_transaction_id. If they don’t match then it’s a restore or renewal.

responseBody.pending_renewal_info Properties

Now, let’s take a look at responseBody.pending_renewal_info properties!

} ], "latest_receipt": "[new]", "pending_renewal_info": [ { "expiration_intent": "1", "auto_renew_product_id": "test.vip.6month.3d.3999.1", "is_in_billing_retry_period": "0", "product_id": "product.name.1", "original_transaction_id": "100000000000000", "auto_renew_status": "0" } ], "status": 0 } Copy

auto_renew_product_id

Identifies customer’s unresolved subscription renewal. You will only see this field if the user has downgraded or crossgraded to a different duration subscription for the following period. 

auto_renew_status

Indicates whether an auto-renewable subscription will renew at the end of the current subscription period. “1” illustrates that it will and “0” shows that the customer has canceled automatic renewal.

If you see the latter, the customer is at high risk of churn and it might be worth presenting him with a special offer.

expiration_intent

Showcases the reason for subscription expiry. Possible values are 1-5.

  • 1 = the customer canceled the subscription themselves. Consider sending a win-back campaign.

  • 2 = there was a billing error. Consider sending a reminder to update billing information to avoid losing access. 

  • 3 = the user didn’t agree to a price increase.

  • 4 = the product was unavailable at the time of renewal.

  • 5 = unknown

grace_period_expires_date, grace_period_expires_date_pst, grace_period_expires_date_ms

Signify the time when the subscription renewal grace period expires.

grace_period_expires_date property has a date-time format similar to that of ISO 8601. grace_period_expires_date_pst is in the Pacific Time zone format and Grace_period_expires_date_ms is in UNIX epoch time format, in milliseconds.

is_in_billing_retry_period

Illustrates if the customer’s auto-renewable subscription is in the billing retry period. Possible values are 1 and 0. The former indicates that the App Store is attempting to renew the subscription while the latter shows it has stopped attempting to renew. If the value is 1, this is a good time to send a billing update request. 

offer_code_ref_name

Showcases the reference name of the subscription offer you set up in App Store Connect. This field is only visible when a subscription offer code was redeemed.

original_transaction_id

Identifies the transaction of the original purchase.

price_consent_status

Indicates the consent status for the subscription price increase. The customer has either consented to the increase or hasn’t. 1 means that the customer has agreed and 0 illustrates that the customer has been notified of the price increase but hasn’t agreed to it yet.

product_id

Identifies that purchased product per your setup in App Store Connect.

Date Format Variations

You have probably noticed that throughout the decrypted receipt there are various date formats:

  • ISO 8601 -like GMT

  • ISO 8601 -like PST

  • UNIX epoch time in milliseconds

But do you already know what these date-time formats mean?

In its documentation, Apple refers to a data-time format similar to the ISO 8601 or an RFC 3339 date. Converting these string-based formats can be difficult, so let’s take a closer look.

As you have seen throughout this document, each base date has two modifiers: _pst and _ms. The former represents ISO 8601-like PST (Pacific Standard Time) and the latter represents milliseconds since UNIX epoch time.

A couple of things you should note:

  • Be extra careful during Pacific Daylight Time (PDT) because the value remains in PST time.

  • The easiest format to work with is UNIX epoch time in milliseconds because the majority of modern programming languages can convert it to a native date-time format.

Improve Your Receipt Management

Congratulations! You got through our rundown of decrypted receipt elements. 

Hopefully, you now have a better grasp on each of them, but don’t worry if things are still a bit overwhelming. You can always bookmark this page for future reference. 

Overall, you’ve probably noticed that decrypted receipts are quite complex. New features are added to the App Store every year and writing good code to manage receipts and leverage everything the Store has to offer is incredibly challenging.

We understand that you may not want to do all of this yourself. That’s precisely why we are here! 

If you want to avoid the hassle with StoreKit or don’t want to build your own subscription validation server, you can always turn to Qonversion! You can head to our Apple Receipt Checker right now or explore the Product Center which provides full in-app purchases infrastructure. As developers, you don’t have to perform this time-consuming receipts management anymore. Leave it to us.

The Ultimate Handbook On App-Store Receipt Validation

Michael Qonversion

Michael

Apr 27, 2021

iOS developers are familiar with StoreKit, a framework that allows them to monetize apps through in-app purchases and subscriptions. For everything to work smoothly, avoid piracy, and make sure users get access to what they purchased developers need to perform receipt validation.

The process begins once a user makes an in-app purchase. After that, developers have to process the App Store receipt, the data of which is Base64-encoded. For a decoded version, you need to pass it through Apple’s verifyReceipt endpoint.

Unfortunately, understanding the decrypted receipt can be an arduous process. There are many nuances and potential pitfalls in the data structure. That’s why, in this article, we will cover each element of the decrypted receipt and help you get a better grasp on how you can avoid common problems. 

The Ultimate Handbook On App-Store

Elements of a Decrypted Receipt

responseBody.Receipt

Now that we’ve covered all the responseBody properties, let’s deep dive into the potential elements of responseBody.Receipt.

This may look overwhelming, but don’t worry. We are going to look over each of these properties in more detail so you can get a better understanding of what they mean.

adam_id and app_item_id

This property is generated by App Store Connect and used to uniquely identify the app corresponding with the receipt. It’s a 64-bit long integer that is assigned solely in production. Thus, expect a unique value in production and a 0 in the sandbox.

application_version

Indicates the app’s version number at the time of the receipt’s receipt_creation_date_ms. It corresponds to CFBundleVersion (iOS) or CFBundleShortVersionString (macOS) in the Info.plist. The value is always “1.0” in the sandbox.

bundle_id

This string is provided on App Store Connect and relates to the value of CFBundleIdentifier in the Info.plist file of the app. It’s a bundle identifier to the app to which the receipt belongs.

download_id

This is a unique identifier for the app download transaction. In the sandbox, the field will be populated with a 0, while in production you will see a unique identifier.

Unfortunately, the download_id is not well covered by Apple. It appears to be connected to the download transaction represented by original_application_version and original_purchase_date.

expiration_date, expiration_date_pst, expiration_date_ms

Illustrate the time at which the receipt expires for apps bought through the Volume Purchase Program. 

For the expiration_date the date-time format is similar to that of ISO 8601. For the expiration_date_pst it’s in the Pacific Time zone. Finally, for expiration_date_ms it’s in UNIX epoch time format, in milliseconds.

in_app

This is an array that contains in-app purchase receipt fields for all in-app purchase transactions.

original_application_version

Indicates the version of the app that the user originally acquired. If the originally purchased version was 2.5, the value in this field would be 2.5, even if currently the application runs on version 5.0. In the sandbox, the value is always “1.0”.

This number corresponds to CFBundleVersion (iOS) or CFBundleShortVersionString (macOS) from the Info.plist.

Original_purchase_date, original_purchase_date_pst, original_purchase_date_ms

Illustrate the time of the original app purchase. 

As was the case with the expiration_date property, the original_purchase_date has the date-time format similar to that of ISO 8601. original_purchase_date_pst is in the Pacific Time zone format. original_purchase_date_ms is in UNIX epoch time format, in milliseconds.

preorder_date, preorder_date_pst, preorder_date_ms

If the app was available for pre-orders, this field would indicate the time the user made the pre-order. 

preorder_date property has a date-time format similar to that of ISO 8601. preorder_date_pst is in the Pacific Time zone format and preorder_date_ms is in UNIX epoch time format, in milliseconds. 

receipt_creation_date, receipt_creation_date_pst, receipt_creation_date_ms

Specify the time at which the App Store generated the receipt.

You probably already got the idea, but let us detail the formats anyway. receipt_creation_date property has a date-time format similar to that of ISO 8601. Receipt_creation_date_pst is in the Pacific Time zone format and receipt_creation_date_ms is in UNIX epoch time format, in milliseconds.

receipt_type

Lists the type of the receipt that was generated. The value relates to the environment in which the app or VPP purchase was made. Possible values include:

  • Production: the receipt was created in the App Store production environment

  • ProductionVPP: the receipt was created in the VPP production environment

  • ProductionSandbox: the receipt was created in the App Store sandbox environment

  • Production VPPSandbox: the receipt was created in the VPP sandbox environment

request_date, request_date_pst, request_date_ms

Show the time the request to the verifyReceipt endpoint was processed and generated a response. 

For the request_date the date-time format is similar to that of ISO 8601. For the request_date_pst it’s in the Pacific Time zone. Finally, for request_date_ms it’s in UNIX epoch time format, in milliseconds.

version_external_identifier

A random number that specifies a revision of the app. In the sandbox, this value is “0”.

responseBody.latest_receipt_info Properties

Time to move on to the properties of responseBody.latest_receipt_info

Hopefully, you already got used to our format. Just like before, let’s explore each of these keys individually.

cancellation_date, cancellation_date_pst, cancellation_date_ms

Indicate the time at which the subscription was canceled. A couple of things can cause this. First, Apple’s customer support team may have refunded the transaction. Second, if Family Sharing is set up, changes to access could lead to subscription cancellation. Finally, the user may have simply upgraded to a different product.

For the cancellation_date the date-time format is similar to that of ISO 8601. For the cancellation_date_pst it’s in the Pacific Time zone. Lastly, for cancellation_date_ms it’s in UNIX epoch time format, in milliseconds.

cancellation_reason

Lists the reason behind the refunded transaction. Possible values are 1 and 0. “1” means that the customer canceled because of an issue within your app. On the other hand, “0” means the cancellation occurred for another reason. Often, if the customer accidentally made the purchase.

expires_date, expires_date_pst, expires_date_ms

Illustrate the time at which the subscription will expire or renew.

Expires_date property has a date-time format similar to that of ISO 8601. expires_date_pst is in the Pacific Time zone format and expires_date_ms is in UNIX epoch time format, in milliseconds.

in_app_ownership_type

In this property, you will see whether the user is the purchaser of the product, or rather a family member with access to it through Family Sharing.

The value will either be FAMILY_SHARED or PURCHASED.

is_in_intro_offer_period

Illustrates if an auto-renewable subscription is in the introductory price period or not. Possible values are true and false. true indicates that the subscription is in fact in an introductory price period, while false negates it. 

is_trial_period

Specifies whether a subscription is in the free trial period or not.

is_upgraded

This property is only present for upgraded transactions and illustrates if a subscription has been canceled because of an upgrade. The value can only be true, signifying that this is what happened.

offer_code_ref_name

Only present when a subscription offer code was redeemed. Indicates the reference name of the offer that was set up in App Store Connect.

original_purchase_date, original_purchase_date_pst, original_purchase_date_ms

Illustrate the time of the original auto-renewable subscription purchase.

original_purchase_date property has a date-time format similar to that of ISO 8601. original_purchase_date_pst is in the Pacific Time zone format and original_purchase_date_ms is in UNIX epoch time format, in milliseconds.

original_transaction_id

Identifies the original purchase transaction.

product_id

Identifies the product purchased as set up for that product in App Store Connect.

promotional_offer_id

Identifies the user’s redeemed subscription offer.

purchase_date, purchase_date_pst, purchase_date_ms

Showcase the time at which the user’s account was charged by the App Store for a subscription purchase or renewal.

Purchase_date property has a date-time format similar to that of ISO 8601. purchase_date_pst is in the Pacific Time zone format and purchase_date_ms is in UNIX epoch time format, in milliseconds.

quantity

Illustrates the number of accessible products purchased. Usually 1-10, typically 1. 

subscription_group_identifier

Identifies the group to which the subscription belongs.

web_order_line_item_id

This is the primary key for identifying subscription purchases. It identifies purchase occurrences across devices.

transaction_id

Identifies purchases, restores, and renewals. The transaction is a purchase when transaction_id matches original_transaction_id. If they don’t match then it’s a restore or renewal.

responseBody.pending_renewal_info Properties

Now, let’s take a look at responseBody.pending_renewal_info properties!

} ], "latest_receipt": "[new]", "pending_renewal_info": [ { "expiration_intent": "1", "auto_renew_product_id": "test.vip.6month.3d.3999.1", "is_in_billing_retry_period": "0", "product_id": "product.name.1", "original_transaction_id": "100000000000000", "auto_renew_status": "0" } ], "status": 0 } Copy

auto_renew_product_id

Identifies customer’s unresolved subscription renewal. You will only see this field if the user has downgraded or crossgraded to a different duration subscription for the following period. 

auto_renew_status

Indicates whether an auto-renewable subscription will renew at the end of the current subscription period. “1” illustrates that it will and “0” shows that the customer has canceled automatic renewal.

If you see the latter, the customer is at high risk of churn and it might be worth presenting him with a special offer.

expiration_intent

Showcases the reason for subscription expiry. Possible values are 1-5.

  • 1 = the customer canceled the subscription themselves. Consider sending a win-back campaign.

  • 2 = there was a billing error. Consider sending a reminder to update billing information to avoid losing access. 

  • 3 = the user didn’t agree to a price increase.

  • 4 = the product was unavailable at the time of renewal.

  • 5 = unknown

grace_period_expires_date, grace_period_expires_date_pst, grace_period_expires_date_ms

Signify the time when the subscription renewal grace period expires.

grace_period_expires_date property has a date-time format similar to that of ISO 8601. grace_period_expires_date_pst is in the Pacific Time zone format and Grace_period_expires_date_ms is in UNIX epoch time format, in milliseconds.

is_in_billing_retry_period

Illustrates if the customer’s auto-renewable subscription is in the billing retry period. Possible values are 1 and 0. The former indicates that the App Store is attempting to renew the subscription while the latter shows it has stopped attempting to renew. If the value is 1, this is a good time to send a billing update request. 

offer_code_ref_name

Showcases the reference name of the subscription offer you set up in App Store Connect. This field is only visible when a subscription offer code was redeemed.

original_transaction_id

Identifies the transaction of the original purchase.

price_consent_status

Indicates the consent status for the subscription price increase. The customer has either consented to the increase or hasn’t. 1 means that the customer has agreed and 0 illustrates that the customer has been notified of the price increase but hasn’t agreed to it yet.

product_id

Identifies that purchased product per your setup in App Store Connect.

Date Format Variations

You have probably noticed that throughout the decrypted receipt there are various date formats:

  • ISO 8601 -like GMT

  • ISO 8601 -like PST

  • UNIX epoch time in milliseconds

But do you already know what these date-time formats mean?

In its documentation, Apple refers to a data-time format similar to the ISO 8601 or an RFC 3339 date. Converting these string-based formats can be difficult, so let’s take a closer look.

As you have seen throughout this document, each base date has two modifiers: _pst and _ms. The former represents ISO 8601-like PST (Pacific Standard Time) and the latter represents milliseconds since UNIX epoch time.

A couple of things you should note:

  • Be extra careful during Pacific Daylight Time (PDT) because the value remains in PST time.

  • The easiest format to work with is UNIX epoch time in milliseconds because the majority of modern programming languages can convert it to a native date-time format.

Improve Your Receipt Management

Congratulations! You got through our rundown of decrypted receipt elements. 

Hopefully, you now have a better grasp on each of them, but don’t worry if things are still a bit overwhelming. You can always bookmark this page for future reference. 

Overall, you’ve probably noticed that decrypted receipts are quite complex. New features are added to the App Store every year and writing good code to manage receipts and leverage everything the Store has to offer is incredibly challenging.

We understand that you may not want to do all of this yourself. That’s precisely why we are here! 

If you want to avoid the hassle with StoreKit or don’t want to build your own subscription validation server, you can always turn to Qonversion! You can head to our Apple Receipt Checker right now or explore the Product Center which provides full in-app purchases infrastructure. As developers, you don’t have to perform this time-consuming receipts management anymore. Leave it to us.

The Ultimate Handbook On App-Store Receipt Validation

Michael Qonversion

Michael

Apr 27, 2021

iOS developers are familiar with StoreKit, a framework that allows them to monetize apps through in-app purchases and subscriptions. For everything to work smoothly, avoid piracy, and make sure users get access to what they purchased developers need to perform receipt validation.

The process begins once a user makes an in-app purchase. After that, developers have to process the App Store receipt, the data of which is Base64-encoded. For a decoded version, you need to pass it through Apple’s verifyReceipt endpoint.

Unfortunately, understanding the decrypted receipt can be an arduous process. There are many nuances and potential pitfalls in the data structure. That’s why, in this article, we will cover each element of the decrypted receipt and help you get a better grasp on how you can avoid common problems. 

The Ultimate Handbook On App-Store

Elements of a Decrypted Receipt

responseBody.Receipt

Now that we’ve covered all the responseBody properties, let’s deep dive into the potential elements of responseBody.Receipt.

This may look overwhelming, but don’t worry. We are going to look over each of these properties in more detail so you can get a better understanding of what they mean.

adam_id and app_item_id

This property is generated by App Store Connect and used to uniquely identify the app corresponding with the receipt. It’s a 64-bit long integer that is assigned solely in production. Thus, expect a unique value in production and a 0 in the sandbox.

application_version

Indicates the app’s version number at the time of the receipt’s receipt_creation_date_ms. It corresponds to CFBundleVersion (iOS) or CFBundleShortVersionString (macOS) in the Info.plist. The value is always “1.0” in the sandbox.

bundle_id

This string is provided on App Store Connect and relates to the value of CFBundleIdentifier in the Info.plist file of the app. It’s a bundle identifier to the app to which the receipt belongs.

download_id

This is a unique identifier for the app download transaction. In the sandbox, the field will be populated with a 0, while in production you will see a unique identifier.

Unfortunately, the download_id is not well covered by Apple. It appears to be connected to the download transaction represented by original_application_version and original_purchase_date.

expiration_date, expiration_date_pst, expiration_date_ms

Illustrate the time at which the receipt expires for apps bought through the Volume Purchase Program. 

For the expiration_date the date-time format is similar to that of ISO 8601. For the expiration_date_pst it’s in the Pacific Time zone. Finally, for expiration_date_ms it’s in UNIX epoch time format, in milliseconds.

in_app

This is an array that contains in-app purchase receipt fields for all in-app purchase transactions.

original_application_version

Indicates the version of the app that the user originally acquired. If the originally purchased version was 2.5, the value in this field would be 2.5, even if currently the application runs on version 5.0. In the sandbox, the value is always “1.0”.

This number corresponds to CFBundleVersion (iOS) or CFBundleShortVersionString (macOS) from the Info.plist.

Original_purchase_date, original_purchase_date_pst, original_purchase_date_ms

Illustrate the time of the original app purchase. 

As was the case with the expiration_date property, the original_purchase_date has the date-time format similar to that of ISO 8601. original_purchase_date_pst is in the Pacific Time zone format. original_purchase_date_ms is in UNIX epoch time format, in milliseconds.

preorder_date, preorder_date_pst, preorder_date_ms

If the app was available for pre-orders, this field would indicate the time the user made the pre-order. 

preorder_date property has a date-time format similar to that of ISO 8601. preorder_date_pst is in the Pacific Time zone format and preorder_date_ms is in UNIX epoch time format, in milliseconds. 

receipt_creation_date, receipt_creation_date_pst, receipt_creation_date_ms

Specify the time at which the App Store generated the receipt.

You probably already got the idea, but let us detail the formats anyway. receipt_creation_date property has a date-time format similar to that of ISO 8601. Receipt_creation_date_pst is in the Pacific Time zone format and receipt_creation_date_ms is in UNIX epoch time format, in milliseconds.

receipt_type

Lists the type of the receipt that was generated. The value relates to the environment in which the app or VPP purchase was made. Possible values include:

  • Production: the receipt was created in the App Store production environment

  • ProductionVPP: the receipt was created in the VPP production environment

  • ProductionSandbox: the receipt was created in the App Store sandbox environment

  • Production VPPSandbox: the receipt was created in the VPP sandbox environment

request_date, request_date_pst, request_date_ms

Show the time the request to the verifyReceipt endpoint was processed and generated a response. 

For the request_date the date-time format is similar to that of ISO 8601. For the request_date_pst it’s in the Pacific Time zone. Finally, for request_date_ms it’s in UNIX epoch time format, in milliseconds.

version_external_identifier

A random number that specifies a revision of the app. In the sandbox, this value is “0”.

responseBody.latest_receipt_info Properties

Time to move on to the properties of responseBody.latest_receipt_info

Hopefully, you already got used to our format. Just like before, let’s explore each of these keys individually.

cancellation_date, cancellation_date_pst, cancellation_date_ms

Indicate the time at which the subscription was canceled. A couple of things can cause this. First, Apple’s customer support team may have refunded the transaction. Second, if Family Sharing is set up, changes to access could lead to subscription cancellation. Finally, the user may have simply upgraded to a different product.

For the cancellation_date the date-time format is similar to that of ISO 8601. For the cancellation_date_pst it’s in the Pacific Time zone. Lastly, for cancellation_date_ms it’s in UNIX epoch time format, in milliseconds.

cancellation_reason

Lists the reason behind the refunded transaction. Possible values are 1 and 0. “1” means that the customer canceled because of an issue within your app. On the other hand, “0” means the cancellation occurred for another reason. Often, if the customer accidentally made the purchase.

expires_date, expires_date_pst, expires_date_ms

Illustrate the time at which the subscription will expire or renew.

Expires_date property has a date-time format similar to that of ISO 8601. expires_date_pst is in the Pacific Time zone format and expires_date_ms is in UNIX epoch time format, in milliseconds.

in_app_ownership_type

In this property, you will see whether the user is the purchaser of the product, or rather a family member with access to it through Family Sharing.

The value will either be FAMILY_SHARED or PURCHASED.

is_in_intro_offer_period

Illustrates if an auto-renewable subscription is in the introductory price period or not. Possible values are true and false. true indicates that the subscription is in fact in an introductory price period, while false negates it. 

is_trial_period

Specifies whether a subscription is in the free trial period or not.

is_upgraded

This property is only present for upgraded transactions and illustrates if a subscription has been canceled because of an upgrade. The value can only be true, signifying that this is what happened.

offer_code_ref_name

Only present when a subscription offer code was redeemed. Indicates the reference name of the offer that was set up in App Store Connect.

original_purchase_date, original_purchase_date_pst, original_purchase_date_ms

Illustrate the time of the original auto-renewable subscription purchase.

original_purchase_date property has a date-time format similar to that of ISO 8601. original_purchase_date_pst is in the Pacific Time zone format and original_purchase_date_ms is in UNIX epoch time format, in milliseconds.

original_transaction_id

Identifies the original purchase transaction.

product_id

Identifies the product purchased as set up for that product in App Store Connect.

promotional_offer_id

Identifies the user’s redeemed subscription offer.

purchase_date, purchase_date_pst, purchase_date_ms

Showcase the time at which the user’s account was charged by the App Store for a subscription purchase or renewal.

Purchase_date property has a date-time format similar to that of ISO 8601. purchase_date_pst is in the Pacific Time zone format and purchase_date_ms is in UNIX epoch time format, in milliseconds.

quantity

Illustrates the number of accessible products purchased. Usually 1-10, typically 1. 

subscription_group_identifier

Identifies the group to which the subscription belongs.

web_order_line_item_id

This is the primary key for identifying subscription purchases. It identifies purchase occurrences across devices.

transaction_id

Identifies purchases, restores, and renewals. The transaction is a purchase when transaction_id matches original_transaction_id. If they don’t match then it’s a restore or renewal.

responseBody.pending_renewal_info Properties

Now, let’s take a look at responseBody.pending_renewal_info properties!

} ], "latest_receipt": "[new]", "pending_renewal_info": [ { "expiration_intent": "1", "auto_renew_product_id": "test.vip.6month.3d.3999.1", "is_in_billing_retry_period": "0", "product_id": "product.name.1", "original_transaction_id": "100000000000000", "auto_renew_status": "0" } ], "status": 0 } Copy

auto_renew_product_id

Identifies customer’s unresolved subscription renewal. You will only see this field if the user has downgraded or crossgraded to a different duration subscription for the following period. 

auto_renew_status

Indicates whether an auto-renewable subscription will renew at the end of the current subscription period. “1” illustrates that it will and “0” shows that the customer has canceled automatic renewal.

If you see the latter, the customer is at high risk of churn and it might be worth presenting him with a special offer.

expiration_intent

Showcases the reason for subscription expiry. Possible values are 1-5.

  • 1 = the customer canceled the subscription themselves. Consider sending a win-back campaign.

  • 2 = there was a billing error. Consider sending a reminder to update billing information to avoid losing access. 

  • 3 = the user didn’t agree to a price increase.

  • 4 = the product was unavailable at the time of renewal.

  • 5 = unknown

grace_period_expires_date, grace_period_expires_date_pst, grace_period_expires_date_ms

Signify the time when the subscription renewal grace period expires.

grace_period_expires_date property has a date-time format similar to that of ISO 8601. grace_period_expires_date_pst is in the Pacific Time zone format and Grace_period_expires_date_ms is in UNIX epoch time format, in milliseconds.

is_in_billing_retry_period

Illustrates if the customer’s auto-renewable subscription is in the billing retry period. Possible values are 1 and 0. The former indicates that the App Store is attempting to renew the subscription while the latter shows it has stopped attempting to renew. If the value is 1, this is a good time to send a billing update request. 

offer_code_ref_name

Showcases the reference name of the subscription offer you set up in App Store Connect. This field is only visible when a subscription offer code was redeemed.

original_transaction_id

Identifies the transaction of the original purchase.

price_consent_status

Indicates the consent status for the subscription price increase. The customer has either consented to the increase or hasn’t. 1 means that the customer has agreed and 0 illustrates that the customer has been notified of the price increase but hasn’t agreed to it yet.

product_id

Identifies that purchased product per your setup in App Store Connect.

Date Format Variations

You have probably noticed that throughout the decrypted receipt there are various date formats:

  • ISO 8601 -like GMT

  • ISO 8601 -like PST

  • UNIX epoch time in milliseconds

But do you already know what these date-time formats mean?

In its documentation, Apple refers to a data-time format similar to the ISO 8601 or an RFC 3339 date. Converting these string-based formats can be difficult, so let’s take a closer look.

As you have seen throughout this document, each base date has two modifiers: _pst and _ms. The former represents ISO 8601-like PST (Pacific Standard Time) and the latter represents milliseconds since UNIX epoch time.

A couple of things you should note:

  • Be extra careful during Pacific Daylight Time (PDT) because the value remains in PST time.

  • The easiest format to work with is UNIX epoch time in milliseconds because the majority of modern programming languages can convert it to a native date-time format.

Improve Your Receipt Management

Congratulations! You got through our rundown of decrypted receipt elements. 

Hopefully, you now have a better grasp on each of them, but don’t worry if things are still a bit overwhelming. You can always bookmark this page for future reference. 

Overall, you’ve probably noticed that decrypted receipts are quite complex. New features are added to the App Store every year and writing good code to manage receipts and leverage everything the Store has to offer is incredibly challenging.

We understand that you may not want to do all of this yourself. That’s precisely why we are here! 

If you want to avoid the hassle with StoreKit or don’t want to build your own subscription validation server, you can always turn to Qonversion! You can head to our Apple Receipt Checker right now or explore the Product Center which provides full in-app purchases infrastructure. As developers, you don’t have to perform this time-consuming receipts management anymore. Leave it to us.

Start Now for Free

Or book a demo with our team to learn more about Qonversion

Start Now for Free

Or book a demo with our team to learn more about Qonversion

Start Now for Free

Or book a demo with our team to learn more about Qonversion

Read more

Read more