Apple Receipt Verification & Validation

The receipt verification service accepts your App's binary receipt data and returns a JSON object according to Apple's Receipt Validation Programming Guide.

URL Endpoint

Accepts base64 binary App Store receipt data.

https://receiptverify.ikonetics.com/api

Encrypted URL Endpoint

Alternate endpoint which encrypts successful results before returning to the client application. This option is only available with higher-tiered paid accounts. See the plan details page for more information.

https://receiptverify.ikonetics.com/apiencrypted

 

Post Attributes

receipt
base64 string

Required. The base64 encoded iOS in-app purchase receipt.

apikey
string

Required. Your account API access key is available on your account screen. It is a unique identifier for your account. We use Google to authenticate you and offer free accounts to all.

token
string

Required. The identifier for one of the Apps configured on your account screen. The receipt details will be compared to the bundle ID of the App as part of the validation process.


 

Returned JSON Properties

receipt

Optional. The decoded receipt returned directly from Apple. This field will not be returned when the result is an error. Always check the status property.

status

For all successful queries this will be 0.

If there are issues with your request this will contain one of the following status codes.
401   - Authentication is incorrect. Check the API key and App token matches your account.
403   - The data was processed, but failed validation checks and may be from a different app. This could indicate an invalid receipt is being used to unlock in-app features.
500   - The server failed, which usually indicates an unexpected response from Apple. Perhaps try again later.

If Apple has an issue with the receipt provided you will receive one of Apple's status code. Apple's Receipt Validation Programming Guide provides a full list of values.

description

Human readable description of the status code. For example, Apple's code 21002 indicates the binary receipt data you provided was malformed or missing.

Encrypted Results

If you used the encrypted endpoint, successful results will be encrypted using the encryption key for your specific app. The encrypted message is base64 encoded into a string before being returned. Decryption is done by:

  1. base64 decode the encrypted message into a byte array
  2. read the first 16 bytes to use as the IV (initialization vector)
  3. read the remaining bytes to use as the encrypted payload (the receipt result)
  4. configure the decryption library with the IV and your app specific encryption key
  5. use the encryption key and IV to decrypt the encrypted payload


 

Sample JSON receipt result

A valid Apple receipt will be similar to the one below. Yours will probably be much larger with more complex items.

{
  "receipt": {
    "receipt_type": "ProductionSandbox",
    "bundle_id": "tld.ikonetics.AppName",
    "request_date": "2014-04-30 15:05:55 Etc/GMT",
    "original_purchase_date": "2014-04-30 15:05:55 Etc/GMT",
    "original_purchase_date_pst": "2012-04-30 08:05:55 America/Los_Angeles",
    "original_purchase_date_ms": "1335098354868",
    "in_app": [
      {
        "quantity": "1",
        "product_id": "tld.ikonetics.AppName.InAppFeature",
        "transaction_id": "1000000336206221",
        "original_transaction_id": "1000000336206221",
        "purchase_date": "2017-09-20 14:43:40 Etc/GMT",
        "purchase_date_ms": "1505918620000",
        "purchase_date_pst": "2017-09-20 07:43:40 America/Los_Angeles",
        "original_purchase_date": "2017-09-20 14:43:40 Etc/GMT",
        "original_purchase_date_ms": "1505918620000",
        "original_purchase_date_pst": "2017-09-20 07:43:40 America/Los_Angeles",
        "is_trial_period": "false"
      }
    ]
  },
  "status": 0,
  "description": "Receipt valid"
}
	  	

The following JSON example shows the result returned when supplying invalid attributes. Notice the error result does not include the receipt.

{
  "status": 401,
  "description": "Authentication is incorrect.",
}
	  	

 

Example iOS Code

This code should be used when your App is responding to transaction statuses, specifically after the SKPaymentTransactionStatePurchased status. More information is available on Apple's In-App Purchase Programming Guide .

The lines marked with TODO indicate areas where you may want to add custom code to handle your specific situation.

// this code example checks a single app-wide receipt for all transactions

NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];

if (!receipt) {
  // TODO: invalid receipt, ask the app to refresh itself, stop and do error handling
  [[[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil] start];
  return;
}


// build the post body with your account api key and app specific token
NSMutableData *postBody = [NSMutableData data];
[postBody appendData: [@"apikey=act_unIquEAccOuntAPIkeySamPle}" dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData: [@"&token=app_SaMpLeAppToKen1" dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData: [@"&receipt=" dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData: [[receipt base64EncodedStringWithOptions:0] dataUsingEncoding:NSUTF8StringEncoding]];

NSString *bodyLength = [NSString stringWithFormat:@"%lu", (unsigned long)[postBody length]];

// setup the a POST request with the post body data
NSURL *apiURL = [NSURL URLWithString:@"https://receiptverify.ikonetics.com/api"];
NSMutableURLRequest *apiRequest = [NSMutableURLRequest requestWithURL:apiURL];
[apiRequest setHTTPMethod: @"POST"];
[apiRequest setValue: @"application/x-www-form-urlencoded" forHTTPHeaderField: @"Content-Type"];
[apiRequest setValue: bodyLength forHTTPHeaderField: @"Content-Length"];
[apiRequest setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData];
[apiRequest setHTTPShouldHandleCookies: NO];
[apiRequest setHTTPBody: postBody];


// create a background session for connecting to the Receipt Verification service.
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *datatask = [session dataTaskWithRequest: apiRequest 
                                            completionHandler: ^(NSData *apiData
                                                                 , NSURLResponse *apiResponse
                                                                 , NSError *conxErr) 
{
    // background datatask completion block
    
    NSError *parseErr;
    NSDictionary *json = [NSJSONSerialization JSONObjectWithData: apiData
                                                         options: 0
                                                           error: &parseErr];

    // TODO: add error handling for conxErr, json parsing, and invalid http response statuscode

    NSDictionary *receipt = [json objectForKey: @"receipt"];
    NSArray <NSDictionary*> *lineitems = [receipt objectForKey: @"latest_receipt_info"];
    if (nil == lineitems) {
        lineitems = [receipt objectForKey: @"in_app"];
    }
        
    /* TODO: Unlock the In App purchases ... 
     At this point the json dictionary will contain the verified receipt from Apple
     and each purchased item will be in the array of lineitems.
     */
 }];


// start the background session datatask
[datatask resume];

		

After you receive the JSON receipt your code should perform some simple checks to verify the JSON receipt from Apple matches your App and your In-App products. For example you may want to compare the transaction's productIdentifier to the product_id in the JSON receipt. Read the Apple documentation to learn other fields you might want to verify before unlocking features.

Assuming you have the productIdentifier from an SKPaymentTransaction and you have a method named unlockFeature, your code may look something like this:

NSDictionary *latest = [json objectForKey:@"latest_receipt_info"];

for (NSDictionary *lineitem in latest) {

  NSString *receiptItem = [lineitem objectForKey:@"product_id"];
  if ([productIdentifier isEqualToString:receiptItem]) {
  
    [self unlockFeature: lineitem ];
    
  }
  
}