Background
We sell a suite of iPadOS/macOS apps that share a single auto-renewable subscription using this architecture.
Per “Offering a Subscription Across Multiple Apps” we require users to sign in before purchasing so we can propagate the entitlement and avoid duplicate subscriptions across apps.
To enforce that sign-in step we plan to turn off Streamlined Purchasing in App Store Connect.
Question
We also want to distribute subscription offer codes (for promotion, retention, appeasing dissatisfied customers, etc.).
After Streamlined Purchasing is turned off, will customers still be able to redeem offer codes outside the app (App Store “Redeem Code” UI or redemption URL)?
If outside-app redemption remains possible, it bypasses our sign-in gate and could let the same customer buy the suite twice (once via each app).
Is there an approved method to limit offer-code redemption to the in-app flow only, or otherwise prevent such duplicate subscriptions?
If no such limitation exists, what best-practice workaround does Apple recommend for multi-app suites that must turn off Streamlined Purchasing yet still wish to use offer codes without duplication risk?
Environment
StoreKit 2; server-side receipt validation & cross-app entitlement propagation.
Apps support the in-app presentCodeRedemptionSheet flow.
We expect to use both one-time-use and custom offer codes.
StoreKit
RSS for tagSupport in-app purchases and interactions with the App Store using StoreKit.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I'm encountering an issue with the App Store Server API where the appAccountToken is not preserved when users migrate their Apple ID email addresses. I've submitted
Feedback Assistant ticket FB18709241 but wanted to check if anyone else has experienced this and get community input on best practices.
The Issue
When a user migrates their Apple ID from one email to another (e.g., from olduser@example.com to newuser@icloud.com), the App Store creates a new subscription
transaction with a different originalTransactionId, but the appAccountToken is not carried forward from the original transaction.
What I'm Seeing
note: these values are fake
When querying /inApps/v1/subscriptions/{originalTransactionId} with the either post-migration transaction ID or the pre-migration transaction ID, the API returns both transactions:
Pre-migration transaction (status: 2 - inactive):
originalTransactionId: "12345678910111"
Contains: "appAccountToken": "abc123-def456-ghi789"
Post-migration transaction (status: 1 - active):
originalTransactionId: "67891011121314"
Missing: appAccountToken entirely
The Problem
The appAccountToken is our only way to link App Store subscriptions to user accounts. Without it on the new transaction:
Users lose access to premium features despite having valid subscriptions
Server-side renewal notifications can't be matched to user accounts
Manual support intervention is required for each affected user
Questions for the Community
Has anyone else encountered this issue with Apple ID migrations?
What's the recommended approach for handling this scenario?
Is there an alternative mechanism to maintain the subscription-to-user linkage across migrations?
Questions for Apple Engineers
Is this the expected behavior, or should the appAccountToken be preserved?
Are there any planned improvements to handle this migration scenario?
What's the best practice for developers to handle this case?
Interestingly, both the old and new transaction IDs return the same JSON response from the App Store Server API, suggesting Apple maintains internal linkage between
these transactions, but the appAccountToken isn't carried forward to the active transaction.
Any insights or similar experiences would be greatly appreciated!
Thank you!!
Feedback Assistant: FB18709241
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
StoreKit
App Store Server Notifications
App Store Server API
It seems that beta 3 broke StoreKit Testing when running against an iOS 26 simulator device. Specifically, when validating product IDs, the debug console displays messages like the following:
[492a4cfa_SK1] Could not parse product: missingValue(for: [StoreKit.ProductResponse.Key.price], expected: StoreKit.BackingValue)
[492a4cfa_SK1] Could not parse product: missingValue(for: [StoreKit.ProductResponse.Key.price], expected: StoreKit.BackingValue)
[492a4cfa_SK1] Could not parse product: missingValue(for: [StoreKit.ProductResponse.Key.price], expected: StoreKit.BackingValue)
[492a4cfa_SK1] Could not parse product: missingValue(for: [StoreKit.ProductResponse.Key.price], expected: StoreKit.BackingValue)
[492a4cfa_SK1] Could not parse product: missingValue(for: [StoreKit.ProductResponse.Key.price], expected: StoreKit.BackingValue)
[492a4cfa_SK1] Could not parse product: missingValue(for: [StoreKit.ProductResponse.Key.price], expected: StoreKit.BackingValue)
[492a4cfa_SK1] Could not parse product: missingValue(for: [StoreKit.ProductResponse.Key.price], expected: StoreKit.BackingValue)
[492a4cfa_SK1] Could not parse product: missingValue(for: [StoreKit.ProductResponse.Key.price], expected: StoreKit.BackingValue)
In addition, the SKProductsResponse (I am using the original StoreKit API), lists all requested product IDs in invalidProductIdentifiers. The products array is empty.
StoreKit Testing behaves as expected when Xcode 26 beta 3 is run against an iOS 18.4 simulator device.
We are seeking clarification on the behavior of App Store Server Notifications V2.
Summary
In our production environment, we received a notification with notificationType: DID_FAIL_TO_RENEW and subtype: GRACE_PERIOD. However, the gracePeriodExpiresDate field in the payload was null.
We understand this notification indicates that a user's subscription has entered a grace period. The null value for its expiration date is unexpected, and we are looking for an official explanation of this behavior and the correct way to handle it.
The Scenario
Here are the details of the notification we received:
Notification Type: DID_FAIL_TO_RENEW
Notification Subtype: GRACE_PERIOD
Environment: Production
Upon decoding the signedRenewalInfo JWS from the responseBodyV2, we found that the gracePeriodExpiresDate field inside the JWSRenewalInfoDecodedPayload was null.
The notificationUUID for this event was in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
Our Implementation and its Impact
Our backend is designed to ensure service continuity during a grace period, as recommended in the documentation.
Current Logic:
Receive the DID_FAIL_TO_RENEW / GRACE_PERIOD notification.
Extract the gracePeriodExpiresDate.
Extend the user's subscription expiration date in our database to match this date.
Because the gracePeriodExpiresDate was null in this case, our logic failed, creating a risk of service interruption for the user.
Context and Investigation
We have performed the following checks:
App Store Connect Settings: We have confirmed that Billing Grace Period is enabled for the relevant subscription group.
Sandbox Environment: We have been unable to reproduce this scenario in the Sandbox.
User Context: We believe the user in this case was experiencing a failed payment when attempting to renew for the first time after a free trial period.
Questions
To ensure we handle this scenario correctly, we would appreciate clarification on the following points:
Conditions for Null: Under what specific conditions does a DID_FAIL_TO_RENEW notification with a GRACE_PERIOD subtype contain a null gracePeriodExpiresDate?
Expected Behavior: Is this null value an expected behavior for certain scenarios, such as the first failed renewal after a free trial?
Best Practice: If this is an expected behavior, what is the correct way to handle it? How should our backend interpret a null gracePeriodExpiresDate to ensure service continuity for the user?
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
In-App Purchase
App Store Server Notifications
Description
SKTestSession.setSimulatedError() does not throw the configured error when testing StoreKit with the .loadProducts API in iOS 26.2. The simulated error is ignored, and products load successfully instead.
Environment
iOS: 26.2 (Simulator)
Xcode: 26.2 beta 2 (Build 17C5038g)
macOS: 15.6.1
Framework: StoreKitTest
Testing Framework: Swift Testing
base project: https://developer.apple.com/documentation/StoreKit/implementing-a-store-in-your-app-using-the-storekit-api
Expected Behavior
After calling session.setSimulatedError(.generic(.notAvailableInStorefront), forAPI: .loadProducts), the subsequent call to Product.products(for:) should throw StoreKitError.notAvailableInStorefront.
Actual Behavior
The error is not thrown. Products load successfully as if setSimulatedError() was never called.
Steps to Reproduce
Create an SKTestSession with a StoreKit configuration file
Call session.setSimulatedError(.generic(.notAvailableInStorefront), forAPI: .loadProducts)
Call Product.products(for:) with a valid product ID
Observe that no error is thrown and the product loads successfully
Sample Code
import StoreKitTest
import Testing
struct SKDemoTests {
let productID: String = "plus.standard"
@Test
func testSimulatedErrorForLoadProducts() async throws {
let storeKitConfigURL = try Self.getStoreKitConfigurationURL()
let session = try SKTestSession(contentsOf: storeKitConfigURL)
try await session.setSimulatedError(
.generic(.notAvailableInStorefront),
forAPI: .loadProducts
)
try await confirmation("StoreKitError throw") { throwStoreKitError in
do {
_ = try await Self.execute(productID: productID)
} catch let error as StoreKitError {
guard case StoreKitError.notAvailableInStorefront = error else {
throw error
}
throwStoreKitError()
} catch {
Issue.record(
"Expect StoreKitError. Error: \(error.localizedDescription)"
)
}
}
#expect(session.allTransactions().isEmpty)
}
static func execute(productID: String) async throws {
guard
let product = try await Product.products(for: [productID]).first(
where: { $0.id == productID })
else {
throw NSError(
domain: "SKDemoTests",
code: 404,
userInfo: [NSLocalizedDescriptionKey: "Product not found for ID: \(productID)"]
)
}
_ = product
}
static func getStoreKitConfigurationURL() throws -> URL {
guard
let bundle = Bundle(identifier: "your.bundle.identifier"),
let url = bundle.url(forResource: "Products", withExtension: "storekit")
else {
fatalError("StoreKit configuration not found")
}
return url
}
}
Test Result
The test fails because the expected StoreKitError.notAvailableInStorefront is never thrown.
Question
Is this a known issue in iOS 26.2 / Xcode 26.2 beta 2, or is there a different approach required for simulating errors with SKTestSession in this version? Any guidance would be appreciated.
Feedback Assistant report: FB21110809
Problem Description:
1、I have two sandbox accounts from different countries. Account A is from Mainland China (CHN), and Account B is from the United States (USA). When I switch the sandbox account from Account A (CHN) to Account B (USA) in the system settings and restart the app, the value of SKPaymentQueue.defaultQueue.storefront.countryCode always returns "CHN" instead of the expected "USA".
2、This issue only occurs on iOS 18.2 and above.
3、On the same device running iOS 17.5.1, the behavior is correct. However, after upgrading the system to iOS 18.3.2, the error occurs.
4、In the sandbox environment, although SKStorefront.countryCode returns the wrong value, the currency type for Apple's in-app purchase is correct when initiating the payment.
5、The issue only exists in the sandbox environment and does not occur in the App Store-downloaded package.
Demo Code:
- (IBAction)clickButton:(id)sender
{
NSString *appStoreCountryCode3 = SKPaymentQueue.defaultQueue.storefront.countryCode;
NSLog(@"%@",appStoreCountryCode3);
}
Demo Testing Steps and Results:
1、Sandbox Account A (China) will print "CHN".
2、Go to Settings - Developer - (at the bottom) SANDBOX APPLE ACCOUNT, and switch to another sandbox account B (USA).
3、Restart the Demo App.
4、Print results:
iOS 17.5.1: "USA" ✅ → Upgrade the system of the same device to iOS 18.3.2 → "CHN" ❌
iOS 18.2.1: "CHN" ❌
iOS 18.3.1: "CHN" ❌
iOS 18.3.2: "CHN" ❌
Possible Clues:
Starting with iOS 18.2, Apple changed the entry point for setting up sandbox accounts, which introduced this bug. It seems that when users switch sandbox accounts on iOS 18.2, Apple engineers forgot to notify the SKStorefront class to update the countryCode value.
Before iOS 18.2: Settings - App Store - Sandbox Account
iOS 18.2 and later: Settings - Developer - (at the bottom) Sandbox Account
Although it doesn't affect the App Store package, it does impact our development and testing process. We hope this issue can be fixed in future versions. Thank you!
I am working on a banking application (includes iPhone and iPad) which includes add to wallet feature.
During the implementation I saw one document it is mentioned that for iPad app, the app must be extended to support Apple pay functionality.
Details from document says "Card Issuers with an iOS mobile banking app must support Card Issuer iOS Wallet extension functionality to enable Card issuer mobile app customers to provision new cards directly from the iOS Wallet app with all eligible Apple iOS devices. If the Card Issuer has a dedicated iPad App, that App must be extended to support Apple Pay functionality. "
Is wallet extension implementation required for Apple pay to work in iPhone and iPad?
Is wallet extension a mandatory implementation for Add to Apple Wallet feature to work and approved by Apple?
I am little confused in this.
Anyone who integrated Apple pay or done add to Apple Wallet feature recently without wallet extension faced any rejection?
Any help would be much appreciated.
I’m testing auto-renewable subscription purchases in the sandbox environment.
When I buy a subscription package using a sandbox test user, I don’t receive any App Store Server Notifications from the sandbox.
However, when I use the “Request Test Notification” option in App Store Connect, the notification is received successfully.
My sandbox server notification URL is configured correctly and publicly accessible.
I also call finishTransaction() after purchase, and the receipt is verified successfully with the sandbox verification endpoint.
To further debug, I used the getNotificationHistory API to fetch notifications for yesterday and today (Nov 3–4, 2025).
Apple’s API response does not include any notifications or transaction IDs for my today’s purchases (Nov 4, 2025) — even though I can confirm from logs that those transactions were completed and verified successfully.
It looks like sandbox purchase notifications are not being sent or logged, even though test notifications work fine.
Could someone from Apple please confirm if there’s currently an issue with sandbox server notifications for auto-renewable subscriptions?
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
App Store Connect
In-App Purchase
Hi!
My product SKU has been approved for Advanced Commerce API. I successfully receive a purchase pop-up with the correct information.
However, I am still having issues with completing the purchase. I always receive Unauthorize error when I confirm the purchase (subscription in my case; see the screenshot). I am using the node.js server library to sign the request. I made sure that the account is a valid account enabled for Sandbox.
Logs unfortunately don't indicate any further detail.
Thanks for your advice! We've been stuck on this for a while now and would appreciate your help.
Marek
The documentation mentions the following:
Verify your receipt first with the production URL; then verify with the sandbox URL if you receive a 21007 status code. This approach ensures you don’t have to switch between URLs while your app is in testing, in review by App Review, or live in the App Store.
This way, you can use one server environment to handle both Sandbox and Production environments. It is necessary to pass App Review.
However, I'm not manually hitting these URLs - I'm using Apple's libraries.
Specifically, the environment is used in SignedDataVerifier and AppStoreServerAPIClient.
(I can't link to these because, for some reason, the domain apple.github.io is not allowed. The documentation for these is only found there. You can find it quickly by searching these terms and the domain.)
Here is how SignedDataVerifier is being used:
const verifier = new SignedDataVerifier(
appleRootCertificates,
APPLE_ENABLE_ONLINE_CHECKS,
APPLE_ENVIRONMENT,
APPLE_BUNDLE_ID,
APPLE_APP_ID
)
const verifiedNotification: ResponseBodyV2DecodedPayload = await verifier.verifyAndDecodeNotification(signedPayload)
if (!verifiedNotification)
{
// Failure
return
}
Here is how AppStoreServerAPIClient is being used:
const appStoreServerAPIClient = new AppStoreServerAPIClient(
SIGNING_KEY,
APPLE_IAP_KEY_ID,
APPLE_IAP_ISSUER_ID,
APPLE_BUNDLE_ID,
APPLE_ENVIRONMENT
)
const statusResponse: StatusResponse = await appStoreServerAPIClient.getAllSubscriptionStatuses(originalTransactionId, [Status.ACTIVE])
In the source code for SignedDataVerifier.verifyAndDecodeNotification, I can see that it throws a VerificationException(VerificationStatus.INVALID_ENVIRONMENT) error .
So for SignedDataVerifier is it as simple as wrapping my code in a try/catch and checking that the error's status code is 21007? I'm unsure about this because if you scroll to the bottom of the linked source code file, you can see the enumeration VerificationStatus, but it's unclear if this member has a value of 21007.
The source code for AppStoreServerAPIClient only says that it throws an APIException if a response could not be processed, so I'm not too sure about how to handle this one.
In a recent update to developer news, Apple posted more details about compliance tools for the new laws coming into effect in Texas on Jan 1.
https://developer.apple.com/news/?id=2ezb6jhj
It is not explicitly stated in the news update or documentation but when the new RESCIND_CONSENT notification is sent to the developer, what happens on the child account devices? Does the app just disappear. Does the developer need to take any action in the App itself?
https://developer.apple.com/documentation/appstoreservernotifications/notificationtype
Thanks,
Eric
Topic:
App & System Services
SubTopic:
StoreKit
We are experiencing an Unknown Error (error code 0) when testing purchases in the Apple Sandbox environment.
The error does not occur every time, but it fails with an Unknown Error in approximately 8 out of 20 attempts.
Due to this issue, our app is failing to pass Apple’s review.
Issue Details
This issue only occurs in the Apple Sandbox environment.
After attempting a purchase, the Apple payment API returns SKPaymentTransaction Unknown Error.
Returned error code: SKErrorUnknown (error.code 0).
This problem is preventing us from conducting proper payment tests and getting approval from Apple.
Would it be possible to receive support or any solutions for this issue?
when we test in app purchase feature in TestFlight, we realize that TestFlight using our real iCloud account in AppStore, but because the app installed from TestFlight, they know it is still "Testing Phase" so they purchase set to FREE.
Now im asking about how to test in app purchase for CANCEL SUBSCRIPTION?
I can't found in my real iCloud account, either for my sandbox account inside AppStore settings.
Please any response will helpful. Thankyou.
Our application is currently under review, and we are still facing issues because we receive a 401 Unauthorized response from the App Store Connect API when using the production environment.
Our app integrates with Chargebee for subscription management, and in production, Chargebee is unable to authenticate with the App Store Server API. This results in a 401 Unauthorized error, preventing the user’s subscription from being synced correctly into our system. Interestingly, the same configuration works in the sandbox environment, but fails in production.
We’ve tried authenticating using JWTs generated from multiple keys (including App Store Connect API / Team Keys with both Admin and App Manager access, and also In-App Purchase keys), all with the same result — sandbox access works, production does not.
Here is our example code for testing with JWT token:
const jwt = require('jsonwebtoken');
const fs = require('fs');
const https = require('https');
const config = {
keyId: '<key_id>',
issuerId: 'issuer_id',
bundleId: 'bundle_id',
privateKey: fs.readFileSync('path_to_key')
};
const { keyId, issuerId, bundleId, privateKey } = config;
const now = Math.floor(Date.now() / 1000);
const jwtToken = jwt.sign(
{
iss: issuerId,
iat: now,
exp: now + 60 * 10, // 10 minutes is fine for test
aud: 'appstoreconnect-v1',
bid: bundleId
},
privateKey,
{
algorithm: 'ES256',
header: {
alg: 'ES256',
kid: keyId,
typ: 'JWT'
}
}
);
console.log('Generated JWT:\n', jwtToken);
// prod
const originalTransactionId = '<prod_transaction_id>';
const hostname = 'api.storekit.itunes.apple.com';
// sandbox
// const originalTransactionId = '<sandbox_transaction_id>';
// const hostname = 'api.storekit-sandbox.itunes.apple.com'
const options = {
hostname,
port: 443,
path: `/inApps/v1/history/${originalTransactionId}`,
method: 'GET',
headers: {
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json',
},
};
const callAppStoreConnectApi = async () => {
const req = https.request(options, (res) => {
console.log(`\nStatus Code: ${res.statusCode}`);
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
console.log('Response Body:\n', data || '[Empty]');
});
});
req.on('error', (e) => {
console.error('Request Error:', e);
});
req.end();
};
callAppStoreConnectApi();
With this code, we were able to authenticate successfully in the sandbox environment, but not in production.
I read in this discussion: https://developer.apple.com/forums/thread/711801 that the issue was resolved once the app was published to the App Store, but I haven’t found any official documentation confirming this.
Does anyone know what the issue could be?
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
StoreKit
App Store Connect
App Store Connect API
We got access into Advanced Commerce API and trying out the server APIs.
I was trying out the Migrate a Subscription to Advanced Commerce API but the API was just simply returning not found to me with a generic error code 4040000 (this is undocumented in the API doc).
Here is the request body
{
"descriptors": {
"description": "User migrated from old plan to Essential",
"displayName": "Essential Plan"
},
"items": [
{
"sku": "com.company.essential",
"description": "A new subscription description after migration",
"displayName": "Essential"
}
],
"requestInfo": {
"requestReferenceId": "11aa3174-9aeb-41a6-996d-fc655a793c06"
},
"storefront": "HKG",
"targetProductId": "com.company.subscription.base",
"taxCode": "C003-00-1"
}
Headers
Authorization: Bearer <REQUEST_TOKEN>
And the response
{
"errorCode": 4040000,
"errorMessage": "Not found."
}
Am I doing something wrong or there will be additional configuration needed?
the email say this notification type will be available to you in production beginning May 27, 2025, and you can test it in sandbox now. but i didn't receice this notification yet
We are in the process of implementing promotional offers for auto-renewable subscriptions in our app using StoreKit 2.
For testing, we use a sandbox user alongside a new user on our platform. I can successfully purchase an Introductory Offer through the app. Once the user is eligible for a Promotional Offer (based on a previous purchase), we retrieve the Promotional Offer identifier and signature from our backend and display the offer.
After initiating the purchase and having the user enter their Sandbox password, the transaction is added to the Payment Queue. However, it fails with the following error:
Purchase failed error: invalidOfferSignature
Additionally, the error returned is:
"Purchase did not return a transaction: Error Domain=ASDServerErrorDomain Code=3903 "Unable to Purchase" UserInfo={NSLocalizedFailureReason=Unable to Purchase, client-environment-type=Sandbox, AMSServerErrorCode=3903, storefront-country-code=IND}"
We are using StoreKit 2 APIs for this process.
Has anyone encountered this issue when working with StoreKit 2, or found a solution to resolve it?
Hi apple team,
I'm using Apple Root Certificates from https://www.apple.com/certificateauthority/ for communicating with App Store Server Library for receipt validation API.
Apple Computer, Inc Root certificate from the website is Not Valid After: Monday, 10 February 2025 at 01:18:14 Central European Standard Time.
When we can expect update of this certificate.
Thank you
My apps stuck on review stage. Reason of it is
Guideline 2.3.2 - Performance - Accurate Metadata
We noticed that your promotional image to be displayed on the App Store does not sufficiently represent the associated promoted in-app purchase. Specifically, we found the following issue with your promotional image: – Your promotional image is the same as your app’s icon.
The problem is i can't change this image. My subscription is still in 'in review' stage and I don't have option like delete image or change image. I replied to the review explaining that I cannot change it as long as the subscription is under review, but I haven’t received any meaningful reply, except that I need to change promotion image to be eligible for further review(which i cant do because i haven't option to change this image). Has anyone had such a problem before?
We have some users who have upgraded to iOS 26 beta3. Currently, we observe that when these users make in-app purchases, our code calls [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; method, and we clearly receive the successful removal callback in the delegate method - (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray<SKPaymentTransaction *> *)transactions. However, when users click on products with the same productId again, the method - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions still returns information about previously removed transactions, preventing users from making further in-app purchases.