Search code examples
iosin-app-purchaseapple-push-notificationsreceipt-validation

Inconsistencies with documentation in response from Apple's verifyReceipt endpoint


I'm in the process of setting up receipt validation for Apple's auto-renewable subscriptions on our server and noticed some inconsistencies with the official documentation. When verifying a sandbox receipt with the sandbox verifyReceipt endpoint, the response looks as follows:

{  
  "auto_renew_status": 1,  
  "status": 0,  
  "auto_renew_product_id": "app.xxx",  
  "receipt": {  
    "original_purchase_date_pst": "2020-03-18 01:11:45 America/Los_Angeles",  
    "quantity": "1",  
    "unique_vendor_identifier": "6D2xxx194",  
    "bvrs": "2",  
    "expires_date_formatted": "2020-03-20 12:27:07 Etc/GMT",  
    "is_in_intro_offer_period": "false",  
    "purchase_date_ms": "1584703627636",  
    "expires_date_formatted_pst": "2020-03-20 05:27:07 America/Los_Angeles",  
    "is_trial_period": "false",  
    "item_id": "15xxx27",  
    "unique_identifier": "cd5xxx424",  
    "original_transaction_id": "100xxx735",  
    "subscription_group_identifier": "20xxx02",  
    "transaction_id": "100xxx439",  
    "web_order_line_item_id": "100xxx419",  
    "version_external_identifier": "0",  
    "purchase_date": "2020-03-20 11:27:07 Etc/GMT",  
    "product_id": "app.xxx",  
    "expires_date": "1584707227636",  
    "original_purchase_date": "2020-03-18 08:11:45 Etc/GMT",  
    "purchase_date_pst": "2020-03-20 04:27:07 America/Los_Angeles",  
    "bid": "app.xxx",  
    "original_purchase_date_ms": "1584519105000"  
  },  
  "latest_receipt_info": {  
    "original_purchase_date_pst": "2020-03-18 01:11:45 America/Los_Angeles",  
    "quantity": "1",  
    "unique_vendor_identifier": "6D2xxx194",  
    "bvrs": "2",  
    "expires_date_formatted": "2020-03-20 12:27:07 Etc/GMT",  
    "is_in_intro_offer_period": "false",  
    "purchase_date_ms": "1584703627000",  
    "expires_date_formatted_pst": "2020-03-20 05:27:07 America/Los_Angeles",  
    "is_trial_period": "false",  
    "item_id": "15xxx27",  
    "unique_identifier": "cd5xxx424",  
    "original_transaction_id": "100xxx735",  
    "subscription_group_identifier": "20xxx02",  
    "transaction_id": "100xxx439",  
    "bid": "app.xxx",  
    "web_order_line_item_id": "100xxx419",  
    "purchase_date": "2020-03-20 11:27:07 Etc/GMT",  
    "product_id": "app.xxx",  
    "expires_date": "1584707227000",  
    "original_purchase_date": "2020-03-18 08:11:45 Etc/GMT",  
    "purchase_date_pst": "2020-03-20 04:27:07 America/Los_Angeles",  
    "original_purchase_date_ms": "1584519105000"  
  },  
  "latest_receipt": "xxx"  
} 

I especially want to point out the following fields of that response:

{  
    ...  
  "latest_receipt_info": {  
      ...  
    "expires_date": "1584707227000",  
    "expires_date_formatted": "2020-03-20 12:27:07 Etc/GMT",  
    "expires_date_formatted_pst": "2020-03-20 05:27:07 America/Los_Angeles",  
    "subscription_group_identifier": "20xxx02",  
    "bid": "app.xxx",  
      ...  
  },  
  "receipt": {  
      ...  
    "expires_date": "1584707227636",  
    "expires_date_formatted": "2020-03-20 12:27:07 Etc/GMT",  
    "expires_date_formatted_pst": "2020-03-20 05:27:07 America/Los_Angeles",  
    "subscription_group_identifier": "20xxx02",  
    "bid": "app.xxx",  
      ...  
  },  
    ...  
} 

The inconsistencies with the official documentation are:

  1. latest_receipt_info is documented to be an array, however, it is a single json object.
  2. In the latest_receipt_info, the expires_date is not in "date-time format similar to the ISO 8601" as the documentation says, but looks like it is in milliseconds since epoch (what should be the expires_date_ms). However, we can find the key expires_date_formatted that is in date-time format.
  3. The same fields as in (2) can be also found in the receipt, however, the documentation states only a key expiration_date (analogue to the expires_date in the latest_receipt_info in date-time format) and expiration_date_ms (in milliseconds since epoch).
  4. The documented bundle_id key (here and here) is not present, but a key bid is, that contains the bundle id.
  5. The key subscription_group_identifer does not contain the exact string entered in AppStoreConnect as subscription group identifer, as documented (here and here), but contains some integer value.

So according to the documentation, the response should look like this, for me:

{  
    ...  
  "latest_receipt_info": [  
    {  
        ...  
      "expires_date": "2020-03-20 12:27:07 Etc/GMT",  
      "expires_date_ms": "1584707227000",  
      "expires_date_pst": "2020-03-20 05:27:07 America/Los_Angeles",  
      "subscription_group_identifier": "MY_SUBSCRIPTION_GROUP_ID",  
      "bundle_id": "app.xxx",  
        ...  
    }  
  ],  
  "receipt": {  
      ...  
    "expiration_date": "2020-03-20 12:27:07 Etc/GMT",  
    "expiration_date_ms": "1584707227636",  
    "expiration_date_pst": "2020-03-20 05:27:07 America/Los_Angeles",  
    "subscription_group_identifier": "MY_SUBSCRIPTION_GROUP_ID",  
    "bundle_id": "app.xxx",  
      ...  
  },  
    ...  
}  
  • I am not sure how to cope with that situation, is that a bug in the API or is the documentation simply wrong?
  • Can I expect the same inconsistencies for the production endpoint (can someone share a sample response please)?
  • How about the notifications in server-to-server notifications, are there also inconsistencies?

Thanks in advance!


Solution

  • For everyone that faces the same problem: We sent the wrong receipt data to our backend since we requested the receipt via the deprecated transactionReceipt and not via appStoreReceiptURL.