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:
- App receives receipt from StoreKit
- App sends receipt to your server
- Your server sends receipt to Apple's
verifyReceiptendpoint - 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:
- Read the receipt from disk
- Parse the PKCS#7 container
- Verify the signature against Apple's root certificate
- 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
Audit your current implementation
Search your codebase for "verifyReceipt", "buy.itunes.apple.com", and "sandbox.itunes.apple.com"
Decide your minimum iOS version
iOS 15+ allows StoreKit 2 (easier). iOS 14 and below require local validation (harder).
Set up App Store Server API (for subscriptions)
Generate API keys in App Store Connect under Users and Access β Keys β In-App Purchase
Implement server-side notifications (optional but recommended)
App Store Server Notifications V2 push subscription events to your server in real-time
Test in sandbox environment
Create sandbox testers in App Store Connect and test all purchase flows
Deploy server changes first
Your server should support both old and new validation during transition
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