We have encountered an issue when verifying transactions using the Get Transaction Info API.
We tested the behavior in both the sandbox and production environments and observed the following results.
When calling the production endpoint:
https://api.storekit.itunes.apple.com/inApps/v1/transactions/{transactionId}
with a transactionId generated in the sandbox environment, the API returns HTTP 401 Unauthorized.
However, based on the documentation and common understanding, we expected HTTP 404 Not Found in this case.
Using the same JWT token, if we call the sandbox endpoint:
https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/{transactionId},
we receive HTTP 200 OK with the expected response body.
We have also confirmed that the same behavior occurs when using the Get Transaction History API — it works correctly in the sandbox environment but returns 401 in production.
Could you please confirm whether this behavior (receiving 401 instead of 404) is expected by design, or if it indicates a potential issue?
If this is not the intended behavior, we would appreciate any guidance or instructions to resolve it.
Thank you very much for your technical support.
「Get Transaction Info」APIを用いてトランザクションの検証を行ったところ、以下の問題が発生しました。
サンドボックス環境および本番環境の両方で検証を行い、次の結果を確認しています。
本番環境エンドポイント https://api.storekit.itunes.apple.com/inApps/v1/transactions/{transactionId}
に対して サンドボックス環境で生成された transactionId を使用すると、HTTP 401 Unauthorized が返却されます。
(一般的には、この場合 404 Not Found が返る想定であると理解しています。)
同一のJWTトークン を用いて サンドボックス環境のエンドポイント
https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/{transactionId}
を呼び出した場合は、HTTP 200 OK が返り、期待通りのレスポンスボディを受け取ることができています。
また、同様の挙動が Get Transaction History を使用した場合にも発生することを確認しています。
サンドボックス環境では正常に動作しますが、本番環境では401が返却されます。
この挙動(401が返却されること)は仕様上想定されたものか、または何らかの問題によるものかご確認をお願いいたします。
もし想定外の挙動である場合は、解決に向けたご案内をいただけますと幸いです。
本件について、技術的なサポートをお願いいたします。
よろしくお願いいたします。
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 using the iOS simulator with a StoreKit configuration file. I can see that there have been transactions while the app has been closed, but my StoreKit 2 listener is never called with those updates to be able to finish them When I open my app from a cold start.
I've added a listener on application(_:didFinishLaunching:launchOptions:) like this:
func startObservingTransactions() {
task = Task(priority: .background) {
for await result in Transaction.updates {
if case .verified(let transaction) = result {
await transaction.finish()
}
}
}
}
But the Transaction.updates loop never gets called (have added breakpoints to check). It's only ever called when a purchase is made, or subsequent transaction renewals when the app is open. Only then it will get the previously unfinished transactions.
Steps to reproduce:
Create an app with a StoreKit config file (with sped up transactions) to purchase an item
Make a purchase then quit the app
Wait for a bit for more transactions to be made while the app is closed.
Open the app from a cold start and none of the transactions will be finished by the listener in your app. Cancel the subscription via the transaction manager.
Close and open the app from a cold start. The first transaction will be finished by the listener but none of the others will be.
In Apple's docs it says
If your app has unfinished transactions, the listener receives them immediately after the app launches
Why is this not the case?
Hi,
Currently, instead of using a real device and test account for in-app purchase testing, we are using Products.storekit with the Simulator.
Our app offers a subscription plan with a 3-day free trial.
We would like to simulate the following test scenarios:
User cancels the subscription within the 3-day free trial period.
User cancels the subscription after the 3-day free trial period.
However, in Xcode, under Debug > StoreKit > Manage Transactions..., we couldn’t find an option to simulate a subscription cancellation.
There is an option to refund the purchase, but we believe this is not the same as a cancellation.
Do you have any idea how we can simulate these two cases using Products.storekit and the Simulator?
Thanks in advance!
I've been struggling to work with the Storekit framework and specifically to find the current Storefront used by the user of the app.
Context : My app needs to behave differently depending on the country of the user.
For me relying on Locale.current.region?.identifier does not seem very reliable, the user can change it really easily.
I'm trying to use the Storekit framework like so :
if let storefront = await StoreKit.Storefront.current{
return storefront.countryCode
}
As per Apple's Storekit documentation :
Use current to determine a customer's current storefront region and offer in-app products suitable for that region. You maintain your own list of product identifiers and the storefronts in which you make them available.
But I just can't find out what I need to change in my current configuration to get another country. The code keeps returning my original storefront (which is France)
I've tried login in with a sandbox user defined on another country. Changed all settings on my device to another country. Changed my Apple's account region as described here. Also tried to logout from everything.
The only thing that works is setting a local .storekit file as described here and changing the default storefront.
Is Xcode overriding the default storefront when building on debug or TestFlight? does anyone know how can I test different storefronts with sandbox users without the local storekit file ?
Thank you in advance.
We’ve recently encountered an increased rate of purchase errors StoreKit.InvalidRequestError error 1 (https://developer.apple.com/documentation/storekit/invalidrequesterror) specifically on iOS 18.4.
We suspect this might be related to the new Advanced Commerce API introduced by Apple, although we haven’t made any changes related to this API in our app.
Have you experienced similar issues since the release of iOS 18.4 or when integrating the Advanced Commerce API? Any insights or suggestions would be greatly appreciated.
Thanks!
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
StoreKit
In-App Purchase
Advanced Commerce API
One of our apps has 85% stuck in Billing Retry -- We are so confused. All the users are from the US, and have a one-week free trial.
We had 1,000 subscriptions expire from this issue.
So any help would be so appreciated.
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
StoreKit
App Store Connect
App Store Server API
Hi everyone,
I’m seeing a consistent one-day discrepancy between the expiresDate returned by the App Store Server API and the “Expires on” date shown in the iOS Settings / App Store subscription list. I’d like to confirm whether this behavior is expected or if I’m misunderstanding the way Apple rounds dates.
Reproduction steps
Step
Action
Result
1
Purchase a 1-month auto-renewable subscription on 23 June 2025 14:00 JST (UTC+9)
Transaction succeeds
2
Immediately fetch the transaction with GET /inApps/v1/subscriptions/{transactionId}
Response contains "expiresDate": "2025-07-23T05:00:00Z" (= 23 July 2025 14:00 JST)
3
On the same device open Settings › Apple ID › Subscriptions (or App Store › Account › Subscriptions)
UI shows Expires on: 22 July 2025
The same happens for every monthly renewal and on multiple devices. Region is Japan, device time zone Asia/Tokyo.
What I understand so far (and my hypothesis)
Apple’s docs say a monthly subscription renews “on the same calendar date” of the next month, so renewal in this example is 23 July.
If the renewal is scheduled for 23 July at 14:00 JST, the subscription is fully usable until the end of 22 July in calendar terms, because the new billing period starts the moment the 23rd begins in Apple’s canonical time zone.
Therefore, it might be intentional for the UI to display 22 July—i.e., “you can keep using it through the 22nd; on the 23rd it renews.”
This hypothesis makes sense internally, yet it still looks confusing to end users who read “Expires on 22 July” and assume access ends at 00:00 on the 22nd, a whole day earlier than in reality.
Questions
Is showing the day before the renewal date the official/expected behavior? If so, could Apple clarify that the “Expires on” label represents the last full calendar day rather than the exact expiry timestamp?
Which value should we surface in-app when telling users “Your subscription is valid until …”?
The server’s expiresDate (precise to the second, converted to user time zone), or
A UI-style date that’s one day earlier, matching Settings / App Store?
Does Apple have a public document describing this rounding/visual convention?
Have other developers encountered user confusion about the apparent 1-day “shortening” and, if so, how did you word your in-app messaging?
Any insight from Apple engineers or fellow developers would be greatly appreciated.
Thank you!
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
App Store
StoreKit
App Store Server API
I’m seeing an issue with subscriptions in TestFlight builds on iOS 26. Running from Xcode works as expected, and the App Store build looks fine. But when I install the same build via TestFlight, transaction.subscriptionStatus is nil.
The identical binary behaves correctly on an iOS 18 device.
Is this expected behavior on iOS 26 TestFlight, or am I missing something?
Thanks!
Hi all,
I am adding the following StoreKit 2 code to my app, and I don't see anything in Apple's documentation that explains the unverified case. When is that case exercised? Is it when someone has tampered with the app receipt? Or is it for more mundane things like poor network connectivity?
// Apple's docstring on `shared` states:
// If your app fails to get an AppTransaction by accessing the shared property, see refresh().
// Source: https://developer.apple.com/documentation/storekit/apptransaction/shared
var appTransaction: VerificationResult<AppTransaction>?
do {
appTransaction = try await AppTransaction.shared
} catch {
appTransaction = try? await AppTransaction.refresh()
}
guard let appTransaction = appTransaction else {
AppLogger.error("Couldn't get the app store transaction")
return false
}
switch appTransaction {
case .unverified(appTransaction, verificationError):
// For what reasons should I expect this branch to be entered in production?
return await inspectAppTransaction(appTransaction, verifiedByApple: false)
case .verified(let appTransaction):
return await inspectAppTransaction(appTransaction, verifiedByApple: true)
}
Thank you,
Lou
We’re testing SKAN postbacks via AdAttributionKit but aren’t receiving any requests on our server even after generating development impressions and triggering a postback.
Setup:
Domain: https://linkrunner-skan.com
Configured in Info.plist as:
<key>NSAdvertisingAttributionReportEndpoint</key>
<string>https://linkrunner-skan.com</string>
<key>AttributionCopyEndpoint</key>
<string>https://linkrunner-skan.com</string>
Apple automatically appends the .well-known paths:
/.well-known/private-click-measurement/report-attribution/
/.well-known/skadnetwork/report-attribution/
ATS diagnostics for the domain: PASS for all tests (TLS 1.0–1.3, PFS disabled, arbitrary loads allowed, etc.)
Both .well-known paths are publicly accessible and return 200 OK
Testing Flow:
Enabled Developer → AdAttributionKit Developer Mode on iOS (15+)
Followed Apple’s official guide: Testing AdAttributionKit with Developer Mode
Generated test impression using:
createAdAttributionKitDevelopmentImpression implemented in SKANManager.swift
Called Postback.updateConversionValue with lockPostback: true
Created Development Postback from Developer Settings
Waited 30+ minutes while intercepting server requests (proxy + backend logs)
What We’ve Tried So Far:
Confirmed ATS compliance with nscurl --ats-diagnostics (all PASS)
Verified .well-known paths are accessible publicly without redirects
Tested endpoints manually with a POST request – server responds 200 OK
Confirmed Info.plist entries exactly match Apple’s required keys
Double-checked iOS device is running iOS 15+ with Developer Mode enabled
Repeated test flow multiple times with fresh impressions and postbacks
Waited up to 1 hour for postback (in case of delays)
Issue:
No POST requests are being received from Apple to either .well-known endpoint, even though the setup appears correct and ATS tests pass.
References Used:
Configuring an Advertised App
Generating JWS Impressions
Question:
Has anyone faced a similar issue where AdAttributionKit Development Postbacks are not firing despite correct Info.plist setup, ATS compliance, and reachable .well-known endpoints?
Any insight into possible missing configuration steps or testing nuances would be greatly appreciated.
In our app we are running into a few issues with pending purchases staying on receipt indefinitely.
These are consumable purchases where we received the purchase succeeded from apple but then something went wrong on our servers to validate and confirm the purchase.
At this point the purchase stays on the apple receipt indefinitely or until we confirm it.
The problem is there are lots of scenarios where we can't confirm purchases anymore (like a game world expired/banned player/etc). So there's a few things I'd like to know to see how this could be handle correctly.
1- Was the user already charged, and if yes would they ever be refunded if the purchase is not confirmed (some sort of expiry)?
2- Is there a way to cancel this sort of pending transaction directly from the app or backend?
3- If one of these users asked for a refund from apple would this clear the purchase from the receipt?
Any information would be greatI couldn't find a lot of info on this topic.
Topic:
App & System Services
SubTopic:
StoreKit
Hi,
We have a app with some auto-renewing subscription in a group of subscriptions.
When a user upgrade from a subscription to another, the "user receive a refund of the prorated amount of their original subscription" (https://developer.apple.com/app-store/subscriptions/).
How is the prorated calculated ?
Example : subscription to 14,99$ / month. If subscriber upgrade after 10 days, is the refund calculated 10/30 of 14,99$ (so ~5$) ?
Hi, I'm reaching out to report a recurring issue with in-app purchases on iOS that seems to be related to Apple’s transaction handling — not to third-party libraries.
In my Flutter application, I use both StoreKit2 and StoreKit1 (for comparison) via different packages, including the official in_app_purchase package. However, in both cases, I’m experiencing unexpected reuse of transactionId and appTransactionId values, even when initiating fresh purchases with unique appAccountToken values.
Problem Summary:
Purchase Stream Returns Old Purchases
When calling buyNonConsumable() with a new product, the purchase stream still returns data for a previously purchased product, despite clearing all Sandbox transactions and using a new applicationUserName for each attempt.
Transaction IDs Reused Across Distinct Purchases
Even when generating a new UUID for appAccountToken on each purchase, the returned appTransactionId and transactionId are reused — this breaks our server-side logic, which expects these fields to uniquely identify purchases and users.
Example Logs:
// First purchase
{
"appAccountToken": "2d5a0880-f68e-44a7-a414-f51204e63904",
"appTransactionId": "704464472748013865",
"transactionId": "2000000928154716"
}
// Second purchase (different user context)
{
"appAccountToken": "2d5a0880-f68e-44a7-a414-f51204e63904",
"appTransactionId": "704464472748013865",
"transactionId": "2000000928429780"
}
Even when using a different productId, the appTransactionId stays the same. When using StoreKit1, the productId updates properly, but the transactionId still matches the previous one.
This behavior also affects App Store Server Notifications (V2): we have observed notifications tied to appAccountTokens from completely different user accounts (based on internal logs), sometimes delayed by days or weeks.
I’ve prepared a reproducible example using the official Flutter in_app_purchase sample with minimal changes — you can find it here:
Github gist
The code is almost identical to the package example. I only added UUID generation for applicationUserName in _getToken(). In the actual app (not in this example), I retrieve the token from an API.
Additional Observations from the Community:
We’ve also found similar issues reported in other frameworks and languages. For instance, a developer using react-native-iap observed that App Store Server Notifications in TestFlight were tied to previously deleted users, even after signing up with a new user account and generating a new appAccountToken. Details here:
User A deleted → User B signs up → receives upgrade event with User A’s token
Notification uses appAccountToken from old account, not the new one
This strengthens the suspicion that the issue may be related to how Apple associates transactions with Apple IDs in test environments.
Questions:
Is it expected for transactionId or appTransactionId to persist across purchases within the same Apple ID, even for different user contexts (e.g., separate logins in the same app)?
Is there any official recommendation for avoiding this kind of data reuse in Sandbox or TestFlight environments?
Should I expect appAccountToken in server notifications to always match the latest value provided during the purchase?
Thank you in advance for your assistance. I would appreciate any clarification or advice regarding this issue, as it impacts production logic that relies on these identifiers being unique and consistent.
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
StoreKit Test
StoreKit
In-App Purchase
Hi everybody 👋 ! Just as the title says, for some reason I can no longer enter my Sandbox account credentials, because the section is gone from the developer settings. I tried reenabling the Developer mode, but with no result. Not a lot of information is available on this topic for the latest iOS versions. Can somebody assist, please?
I am using the public url https://api.storekit-sandbox.itunes.apple.com/inApps/v1/notifications/jwsPublicKeys to get the jwks keys to verify the signed payload for store kit payments. I am checking Apple server notifications.
const APPLE_JWKS_URL = "https://api.storekit-sandbox.itunes.apple.com/inApps/v1/notifications/jwsPublicKeys"
// Apple JWK set (cached by jose)
const appleJWKS = createRemoteJWKSet(new URL(APPLE_JWKS_URL));
const jwks = await appleJWKS();
logger.debug("Apple JWKS Keys: %O", jwks); // Log the keys
if (!signedPayload) {
// return res.status(400).json({ error: "Missing signedPayload" });
}
// Step 1: Verify JWS (signature + payload) using Apple's JWKS
const { payload, protectedHeader } = await jwtVerify(
signedPayload,
appleJWKS,
{
algorithms: ["ES256"], // Apple uses ES256 for signing
}
);
Hello!
The localization isn't working when using SubscriptionStoreView. The app hasn't been published yet. The subscription has been created and localization strings have been added. Status - ready to submit.
Testing environment: Sandbox
When calling SubscriptionStoreView, the debug console shows this error:
GenerativeModelsAvailability.Parameters: Initialized with invalid language code: ru-RU. Expected to receive two-letter ISO 639 code. e.g. 'zh' or 'en'. Falling back to: ru
Despite this, the subscription interface appears in English when Russian is expected.
I don't use any locale setting for ru-RU anywhere in my code. The test device's region is set to Russia, and the language is Russian.
Any help would be appreciated.
Dear Apple Support Team,
We are currently implementing auto-renewable subscriptions in our iOS app and are testing the integration using the sandbox environment.
On the iOS app side, the in-app purchase flow completes successfully and displays a "Purchase Successful" message. However, we are not receiving any server notification callbacks on our configured App Store Server Notifications (Sandbox) webhook URL.
For your reference, the webhook URL we have set in App Store Connect (Sandbox) is:
https://9c0f-182-79-123-254.ngrok-free.app/ios/webhook
Despite successfully completing a subscription purchase in the sandbox, there is no evidence that the webhook is being triggered.
We would appreciate your guidance in resolving this issue or confirming if there are any additional configurations or steps required on our end.
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
StoreKit Test
StoreKit
In-App Purchase
App Store Receipts
Whether using Storefront.current?.countryCode or SKPaymentQueue.default().storefront?.countryCode, both are returning "USA" only.
(It used to return the correct country code before the update.)
In the sandbox environment, the country code is returned correctly,
but in the TestFlight environment, it always returns "USA".
There's no mention of this behavior in the beta release notes, so I'm posting it here for visibility.
We offer a 3-day free trial, and our paywall clearly states that users will be charged after the trial ends.
However, some users request refunds after the charge - even after fully using our app for days or even weeks. In some cases, refunds are approved despite the users having consumed our AI processing services for up to a month.
Since our app relies on backend AI processing, each user session incurs a real cost. To prevent losses, we utilize RevenueCat’s CONSUMPTION_REQUEST system and have set our refundPreference to: "2. You prefer that Apple declines the refund".
Until recently, Apple typically respected this preference, and 90% of refund requests were declined as intended.
However, starting about a week ago, we observed a sudden reversal: Apple is now approving around 90% of refund requests, despite our refund preference. As a result, we are operating at a loss and have had to halt both our marketing campaigns and our 3-day free trial.
We’re trying to understand whether this shift is due to a change in Apple’s refund policy, or if we need to handle CONSUMPTION_REQUEST differently on our end.
Has anyone else experienced similar changes? Any insights would be greatly appreciated.
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
StoreKit
App Store Server Notifications
App Store Server Library
This is a copy of a reply to this post.
https://developer.apple.com/forums/thread/722222?page=1
I'm posting as new in the hope someone might have more up-to-date information, as I'm pulling out what little hair I have left.
I'm using Storekit 2, testing in Xcode with a local Storekit config file. I have created a very minimal system to investigate this issue. I have a SwiftUI-based window using SubscriptionStoreView, and my app set up with the usual listener. I have four types of auto renewing subscription, configured in the local Storekit config file.
With my app running, I subscribe to the lowest-level subscription I offer, via the SubscriptionStoreView. Notification of the inital purchase arrives, but subsequent auto-renewals do not trigger any action in my listener for Transaction.updates. They arrive as expected in the Transaction Manager. Radio silence in my listener.
If I upgrade one subscription (via my SubscriptionStoreView) I see this reflected in the UI immediately, and also in the Transaction Manager, but the update that arrives in Transaction.updates refers to the old subscription, and has the isUpgraded flag set to false.
Also, can anyone remind me what the grey warning triangle next to entries in the Transaction Manager means. I'm assuming it means unfinished, as that's what the sidebar indicates.
Can the testing system really be this broken, or am I wildly off the mark? Unless I'm doing something fundamentally wrong this all seems extremely flakey, but happy to be proved wrong.
I find this all rather unsettling if I can't test reliably, and am concerned that I my app may end up in this situation if I use storekit 2:
https://stackoverflow.com/questions/73530849/storekit-renewal-transactions-missing-in-transaction-all-or-transaction-updates