Executive summary
- What: when an app migrates a non expiring offline token to an expiring offline access token without a user session, an eligible retry with the original token now returns the same access and refresh token pair for up to seven days.
- Why: a migration response that is lost before it is persisted no longer forces a merchant to reopen the app and authorize again.
- Who: developers of apps still holding non expiring offline tokens, especially background integrations with no regular admin session.
What changed
The direct migration flow exists so apps need not wait for a user to open the app before moving to expiring tokens. Its weakness was a single use response: lose it to a failed database write or a timeout and the store was stranded until a merchant triggered a fresh authorization.
A retry using the same original token and the same client credentials is now idempotent within a window. Shopify returns the same access token and refresh token, and extends the access token expiry where needed. It does not extend the refresh token expiry, so the refresh clock started at the first exchange. Recovery ends at whichever comes first: seven days from the initial exchange, a successful refresh of the issued pair, or a later token acquisition for that store replacing the pair, such as an ID token exchange or authorization code grant.
Existing flows need no changes. The original non expiring token remains invalid for Admin GraphQL API requests once migrated, so it is a recovery key only, never a fallback credential. A retry answering with invalid_subject_token means the window has closed and you need a fresh token through ID token exchange or the authorization code grant.
Why it matters
The apps most exposed are the ones merchants notice least until they break: ERP and OMS connectors, inventory and order sync services, scheduled middleware with no human in the admin. For those, asking a merchant to reopen an app means a support ticket, a wait on someone with the right permissions, and a sync gap in between.
A seven day idempotent recovery window turns a class of unrecoverable failure into an operational retry. Build around it deliberately rather than treating it as a safety net, because the window is bounded and one successful refresh closes it. For merchants the benefit is fewer silent integration outages during a migration that should be invisible. Teams weighing how much of this to own internally can see how we approach it on the Shopify development side.
Role-specific impact
- Marketers: no direct effect, though a stalled integration surfaces as stale inventory or delayed order data in the tools campaigns depend on.
- Developers: treat the retry as a bounded recovery path, not durable state. Persist access token, refresh token and expiry in one atomic write, log the exchange timestamp per store, and handle invalid_subject_token as the signal to acquire a fresh token.
- Store admins: expect fewer requests to reopen an app and reauthorize during token migrations. When one arrives, the window has closed and the request is genuine.
Use-case example
Real-world scenario
An app vendor migrates offline tokens across roughly 200 merchant stores in one batch. Connection pool exhaustion partway through drops the persistence step for about 40 stores while the exchanges succeed. Previously each of those merchants had to reopen the app, which can take days where the only authorized admin is an ops manager. With the retry window the vendor replays those 40 exchanges with the original tokens, persists correctly, and contacts no merchant. Roughly 40 support conversations are avoided.
Implementation checklist
- Identify every store where your app holds a non expiring offline token.
- Make persistence atomic: store access token, refresh token and expiry together, or not at all.
- Record the initial exchange timestamp per store to know how much of the seven day window remains.
- Build the retry path to reuse the original token and the same client credentials.
- On invalid_subject_token, acquire a new token through ID token exchange or the authorization code grant and update stored credentials.
- Discard the original token once migration is confirmed, since it no longer works for Admin GraphQL API requests.
- Migrate in cohorts small enough that a persistence failure stays inside the window.
FAQ
Q: Does a retry extend the refresh token expiry?
A: No. Shopify returns the same pair and extends the access token expiry where needed, but not the refresh token expiry. The refresh clock runs from the initial exchange.
Q: Can we keep using the original non expiring token as a fallback?
A: No. Once migrated it is invalid for Admin GraphQL API requests. Its only remaining use is as the subject token on an eligible retry inside the seven day window.
Resources
Migrating existing tokens without a user session
Need guidance? Talk to Makro.