Context
We're handling App Store subscriptions on the server side using App Store Server Notification v2. Our pipeline currently identifies each event by transactionId and originalTransactionId.
A few notes about our client:
- Our app is built with Flutter and uses the standard
in_app_purchaseplugin layer to drive App Store purchases (StoreKit 1 under the hood). - We have not migrated to StoreKit 2 on the client yet.
- We have not been setting
SKPayment.applicationUsernameon outgoing purchases, so every transaction we've ever produced hasappAccountToken: nullin its v2 notification.
This question is purely about what the server-side notification can tell us, given the current client state above.
What we're trying to figure out
A user can resubscribe to an expired subscription in two different places:
- In-app — the user opens our app and re-purchases through our normal in-app purchase flow.
- App Store — the user goes to Settings → Apple ID → Subscriptions and resubscribes from the system UI, without ever returning to the app.
Both paths trigger a SUBSCRIBED notification (subtype RESUBSCRIBE) with structurally identical payloads as far as we can tell — same shape for data.transactionInfo, data.renewalInfo, etc. From the notification alone we can't decide which path produced it.
The reason this matters: in our system, the two paths require different business handling:
- In-app path: the user may have signed in to a different business account in our app. The new subscription should be attributed to whoever paid in the app just now, not to the previous owner of
originalTransactionId. - App Store path: there is no in-app signal, so the business owner can only be inferred from the previous
originalTransactionIdmapping.
If we get it wrong, the subscription's entitlement ends up on the wrong business account.
What we do today
Because we can't tell the paths apart from the notification, we defer processing for a few minutes and check whether an in-app order for the same transaction has arrived in the meantime:
- If an in-app order shows up → it's the in-app path; attribute to the in-app account.
- If nothing shows up after the delay → assume App Store path; fall back to the previous owner mapping.
This works but adds latency to entitlement activation and forces us to build a deferred-retry queue with idempotency against the in-app callback path.
Possible direction: appAccountToken / applicationUsername
We noticed that v2 notifications carry transactionInfo.appAccountToken, and the docs suggest that StoreKit 1's SKPayment.applicationUsername (when it's a valid UUID) is mirrored into this field. In theory, if we start setting it on every in-app purchase from the Flutter client, the field could double as a path discriminator on the server:
appAccountToken != null→ in-app path (only the app can set it), and we even get the business user id for freeappAccountToken == null→ App Store path (no UI to populate it)
But we have some open questions before committing to this direction:
Questions
-
Is there an existing signal in
ResponseBodyV2/JWSTransactionDecodedPayload/JWSRenewalInfoDecodedPayloadthat distinguishes these two paths, that I might be missing? -
Can the same distinction be obtained via
getAllSubscriptionStatuses/getTransactionHistory/ any other Server API endpoint? -
Is
applicationUsername(StoreKit 1) still a reliable way to populateappAccountTokenon v2 notifications today? Specifically:- Are there format constraints beyond "valid UUID" that cause Apple to drop the value?
- Any known differences between sandbox and production in how it's mirrored?
- Does the App Store path ever strip or overwrite a previously-set value when the same
originalTransactionIdis reused?
-
For existing subscriptions where
applicationUsernamewas never set (which is all of ours today, since we've never polient), is there any way to retroactively distinguish the in-app vs App Store path? Or is timing-based deferred matching theonly option for that cohort, even after we start setting the value on new purchases? -
If neither (1) nor (2) is currently possible, is the timing-based heuristic we use today the pattern Apple expects developers to follow, or is there a recommended approach we're missing?
A small suggestion, if it turns out there's no existing way
If the information genuinely isn't exposed today, it might be worth surfacing a salesChannel-style field on the transaction, similar to what Google Play Developer API exposes on Order.salesChannel (IN_APP, PLAY_STORE, etc.). That would let server-side handlers route each event to the correct business owner immediately, regardless of whether appAccountToken was set, and would also cover legacynt never had a chance to populate it.
Thanks — happy to share sample payloads or more detail if helpful.