Special Year-End Offer: AI Review Toolkit $29.99 $49.99 Get it now β†’

iOS Submission Guide

Urgent Deadline: January 24, 2025

Apple's verifyReceipt endpoint changes take effect on this date. If your app still relies on the old validation method, in-app purchases could break for users.

If you've ever validated App Store receipts, you probably have code calling Apple's verifyReceipt endpoint. That endpoint is going away. Here's what's happening and exactly how to fix it before your IAP breaks.

πŸ“‹ TL;DR - What You Need to Know

  • β€’ Apple deprecated verifyReceipt - stop sending receipts to Apple's servers
  • β€’ New method: Validate receipts locally using SHA-256 hash comparison
  • β€’ Server-side: Use App Store Server API for subscription status
  • β€’ StoreKit 2 handles most of this automatically on iOS 15+
  • β€’ January 24, 2025 is when verifyReceipt behavior changes

Why Apple Made This Change

Let me explain what happened, because the announcement was buried in WWDC sessions and technical notes that most developers missed.

For years, the "correct" way to validate receipts was:

  1. App receives receipt from StoreKit
  2. App sends receipt to your server
  3. Your server sends receipt to Apple's verifyReceipt endpoint
  4. Apple returns validation result

This worked, but it had problems. The endpoint was slow (sometimes taking seconds to respond), it was a single point of failure, and it created unnecessary server-to-server traffic for what should be a simple cryptographic verification.

Apple's new approach is more elegant: receipts are signed using Apple's certificate chain. You can validate them locally by checking the signature, without calling any Apple servers. For subscription status and purchase history, there's a separate App Store Server API.

What Breaks If You Don't Migrate

Here's the timeline:

Date What Happens
Now verifyReceipt still works but is deprecated
Jan 24, 2025 verifyReceipt stops returning receipt data for new purchases
Mid 2025 verifyReceipt endpoint fully retired

If your app relies on verifyReceipt after January 24, 2025:

  • New purchases won't validate correctly
  • Subscription renewals might not be recognized
  • Users who bought premium features see them locked
  • Support tickets will flood in

The Migration Path

Your migration depends on your current setup. Let me break this down by scenario.

Scenario 1: Using StoreKit 2 (iOS 15+)

Good news - you're mostly set. StoreKit 2 uses JWS (JSON Web Signature) transactions that are automatically validated. The receipt file at Bundle.main.appStoreReceiptURL is deprecated in StoreKit 2.

What you should be doing:

// StoreKit 2 - transactions are self-validating
for await result in Transaction.currentEntitlements {
    switch result {
    case .verified(let transaction):
        // Transaction is already verified by StoreKit 2
        // Grant access based on transaction.productID
        await updateEntitlements(for: transaction)
    case .unverified(let transaction, let error):
        // Handle verification failure
        Logger.iap.error("Unverified transaction: \(error)")
    }
}

For server-side validation with StoreKit 2, send the originalTransactionId and signedTransactionInfo to your server, then use the App Store Server API to get full purchase history.

Scenario 2: Still on StoreKit 1 (Original API)

This is where most developers need to make changes. You have two options:

Option A: Migrate to StoreKit 2

This is the recommended long-term solution. StoreKit 2 has better APIs and automatic validation. The catch is it requires iOS 15+, so you might need to drop support for older devices.

Option B: Local Receipt Validation

If you need to support iOS 14 and earlier, implement local receipt validation. This involves:

  1. Read the receipt from disk
  2. Parse the PKCS#7 container
  3. Verify the signature against Apple's root certificate
  4. Extract and validate the receipt fields

Here's a simplified overview (the full implementation requires OpenSSL or similar):

import Foundation
import CryptoKit

class LocalReceiptValidator {

    // Apple's root certificate (bundled in your app)
    private let appleRootCertificate: SecCertificate

    func validateReceipt() throws -> Receipt {
        // 1. Get receipt data
        guard let receiptURL = Bundle.main.appStoreReceiptURL,
              let receiptData = try? Data(contentsOf: receiptURL) else {
            throw ReceiptError.notFound
        }

        // 2. Verify PKCS#7 signature
        // This requires OpenSSL or a similar crypto library
        let isSignatureValid = try verifyPKCS7Signature(
            data: receiptData,
            rootCertificate: appleRootCertificate
        )

        guard isSignatureValid else {
            throw ReceiptError.invalidSignature
        }

        // 3. Extract and parse receipt payload
        let receipt = try parseReceiptPayload(receiptData)

        // 4. Verify bundle ID matches your app
        guard receipt.bundleId == Bundle.main.bundleIdentifier else {
            throw ReceiptError.bundleMismatch
        }

        // 5. Verify receipt hash (SHA-256)
        try verifyReceiptHash(receipt: receipt)

        return receipt
    }

    private func verifyReceiptHash(receipt: Receipt) throws {
        // Get device identifier
        let deviceId = getDeviceIdentifier()

        // Compute expected hash
        var dataToHash = Data()
        dataToHash.append(deviceId)
        dataToHash.append(receipt.opaqueValue)
        dataToHash.append(receipt.bundleIdData)

        let computedHash = SHA256.hash(data: dataToHash)

        guard computedHash == receipt.sha256Hash else {
            throw ReceiptError.hashMismatch
        }
    }
}

Important: SHA-256 Hash Verification

The receipt contains a SHA-256 hash computed from the device GUID, opaque value, and bundle ID. You must verify this hash matches to ensure the receipt belongs to this device and wasn't copied from another device.

Scenario 3: Server-Side Subscription Management

For subscriptions, you should use the App Store Server API instead of verifyReceipt:

// Node.js example using App Store Server API
import { AppStoreServerAPIClient, Environment } from '@apple/app-store-server-library';

const client = new AppStoreServerAPIClient(
    signingKey,          // Your private key (.p8 file)
    keyId,               // Key ID from App Store Connect
    issuerId,            // Issuer ID from App Store Connect
    bundleId,
    Environment.PRODUCTION
);

async function getSubscriptionStatus(originalTransactionId: string) {
    try {
        const response = await client.getAllSubscriptionStatuses(originalTransactionId);

        for (const subscription of response.data) {
            const status = subscription.lastTransactions[0]?.status;
            // 1 = Active, 2 = Expired, 3 = Billing Retry, 4 = Grace Period, 5 = Revoked
            console.log(`Subscription status: ${status}`);
        }

        return response;
    } catch (error) {
        console.error('Failed to get subscription status:', error);
        throw error;
    }
}

Step-by-Step Migration Checklist

1

Audit your current implementation

Search your codebase for "verifyReceipt", "buy.itunes.apple.com", and "sandbox.itunes.apple.com"

2

Decide your minimum iOS version

iOS 15+ allows StoreKit 2 (easier). iOS 14 and below require local validation (harder).

3

Set up App Store Server API (for subscriptions)

Generate API keys in App Store Connect under Users and Access β†’ Keys β†’ In-App Purchase

4

Implement server-side notifications (optional but recommended)

App Store Server Notifications V2 push subscription events to your server in real-time

5

Test in sandbox environment

Create sandbox testers in App Store Connect and test all purchase flows

6

Deploy server changes first

Your server should support both old and new validation during transition

7

Submit app update

Push the update well before January 24, 2025 to account for review time

Libraries That Help

You don't have to implement receipt validation from scratch. Here are battle-tested options:

Library Platform Notes
RevenueCat iOS, Android, Web Full-service subscription management. Handles migration automatically.
app-store-server-library Node.js, Python, Java, Swift Apple's official libraries for App Store Server API
TPInAppReceipt iOS (Swift) Pure Swift local receipt validation, no OpenSSL dependency
Qonversion iOS, Android Alternative to RevenueCat with analytics focus

Common Mistakes to Avoid

❌ Hardcoding Apple's root certificate

Apple's certificate has an expiration date. Bundle it in a way that allows updates, or fetch from a known URL during validation.

❌ Trusting originalTransactionId from the client

Always verify on your server. Clients can send any transaction ID. Use signed data or the App Store Server API to confirm.

❌ Not handling the sandbox/production split

App Store Server API has separate URLs for sandbox and production. Auto-detect based on receipt environment.

❌ Waiting until the deadline

App Review takes time. Submit your update at least 2 weeks before January 24, 2025.

Testing Your Migration

Before submitting, verify these scenarios work:

  • New purchase: User buys a product for the first time
  • Restore purchases: User reinstalls app and restores
  • Subscription renewal: Sandbox subscriptions renew every few minutes
  • Subscription expiration: Let a sandbox subscription lapse
  • Upgrade/downgrade: User changes subscription tier
  • Refund: Simulate via App Store Connect sandbox
  • Family sharing: If enabled for your products

Pro Tip: Test with StoreKit Configuration Files

Xcode 14+ lets you create StoreKit configuration files for local testing without needing App Store Connect. This speeds up development significantly. Go to File β†’ New β†’ File β†’ StoreKit Configuration File.

FAQ

What if I'm using a third-party SDK like RevenueCat?

Update to their latest SDK version. They've already migrated away from verifyReceipt. Check their documentation for any additional steps.

Do I need to migrate if I only have consumable IAPs (no subscriptions)?

Yes. The verifyReceipt deprecation affects all receipt validation, not just subscriptions. Local validation or StoreKit 2 applies to consumables too.

Can I still support iOS 13/14 users?

Yes, using local receipt validation. StoreKit 2 is iOS 15+ only, but the original StoreKit with local validation works on older versions.

What's the difference between App Store Server API and verifyReceipt?

verifyReceipt took a receipt and returned validation info. App Store Server API takes a transaction ID and returns subscription status, transaction history, etc. It's more granular and efficient.

Will my existing users be affected?

Only if they make a new purchase or renewal after January 24, 2025, and they're on an old app version that uses verifyReceipt. Push the update ASAP to minimize this window.

Need Help With IAP Compliance?

Our AI Review Toolkit includes prompts specifically designed to audit your in-app purchase implementation. Get the toolkit and ensure your IAP code meets Apple's requirements before you submit.

Get the AI Toolkit

Want AI to audit your app before submission?

Get our AI Review Toolkit with prompts that catch guideline violations automatically.

Get the AI Toolkit