Setup: App using expiring offline access tokens, refreshing server-side near expiry with grant_type=refresh_token against POST /admin/oauth/access_token.
Issue: for a brief window this endpoint returned HTTP 520 (“error code: 520”) for a shop:
[2026-06-15 10:54:07 UTC] POST https://{shop}.myshopify.com/admin/oauth/access_token -> 520 <none>
[2026-06-15 10:54:25 UTC] POST https://{shop}.myshopify.com/admin/oauth/access_token -> 520 <none>
After that, the shop’s offline token was permanently orphaned — every subsequent refresh returned:
POST https://{shop}.myshopify.com/admin/oauth/access_token -> 401
{"error":"invalid_request","error_description":"This request requires an active refresh_token"}
Refresh tokens are single-use / rotating, so it looks like the 520 hit after the old token was consumed/rotated server-side but before the new token was returned — leaving the token gone with no way to recover via refresh_token. The merchant was locked out for ~2 hours until we re-minted via token exchange.
Flagging the 520s on the token endpoint so the team can take a look — a server error there shouldn’t happen, and during refresh rotation it can permanently lock a merchant out. Happy to share exact timestamps / shop / request IDs.
This is actually my biggest concern with expiring token migrations. At the beginning of June, I was about to migrate users on one of my apps when Shopify experienced a major API outage. The same thing happened again this morning: I was preparing another migration and started seeing intermittent API failures with responses such as “Internal error. Looks like something went wrong on our end” with a request ID to forward to the Shopify support.
If these failures occur during the refresh token rotation process, the consequences can be severe. My concern is that a refresh token could be consumed server-side while the response never reaches the app due to a timeout, 520, or internal error. In that scenario, the merchant could end up permanently locked out and forced to reinstall the app to obtain a new token.
For large-scale migrations, that’s a pretty scary failure mode. We really need a mechanism that guarantees token refreshes are atomic and recoverable, even when Shopify or the network experiences transient failures. A server-side error during token rotation should never leave an app without a valid way to authenticate.
The shop then sat idle (no API calls), so no refresh happened during the normal near-expiry window.
The refresh fired lazily on the merchant’s first request after reopening the app the two 520s were at 10:54:07 and 10:54:25 UTC, i.e. ~6h50m after the access token had already expired.
So this wasn’t a near-boundary race (token expiring mid-flight). The access token was stale-and-unused, and the very first refresh attempt to revive it is the one that caught the transient 520 which then consumed/rotated the refresh token without returning the replacement.
we have had a similar issue, access token expired about a week ago, refresh token still 3 months til expiry, unfortunately wasn’t able to get the error message, but we do retry if any issues and have ended up with stale access and refresh tokens.
I am experiencing the same thing. In my case, I have tried to test regenerating the refresh token, but it returned the same response {"error":"invalid_request","error_description":"This request requires an active refresh_token"}with 401 status code where the refresh token is just generated.
Hope to find a solid solution. Please correct me if I am doing anything wrong here to regenerate.
@Liam-Shopify We run a public payments app. The expiring offline token changelog is tagged Admin API only and doesn’t appear under the payments-apps-api filter, but our API health warning is triggered by Payments Apps API calls (paymentSessionResolve) made with a non-expiring offline token. Support told us it applies here too, since it’s the same X-Shopify-Access-Token.
Can someone confirm:
Does the requirement apply to Payments Apps API calls, or only Admin API?
After Jan 1, 2027, will non-expiring offline tokens stop working for paymentSessionResolve?
paymentSessionResolve is synchronous in checkout (its response returns the confirmation redirectUrl). For a low-volume shop whose 90-day refresh token expires during a no-order period, what’s the recommended way to avoid the next customer’s checkout failing with no merchant present to re-auth?
A bit more detail on the expected behavior and how apps should handle ambiguous refresh failures:
If a refresh request times out, encounters a network error, or returns a transient 5xx response, you can retry the request using the original refresh token. During the retry window, if that token was already rotated, the retry returns the same newly issued credentials rather than rotating them again.
Note: You should still treat the previous refresh token as spent once the new credentials are successfully received. The retry window is intended to accommodate normal retry and backoff behavior, rather than provide a fixed period that apps should build around.
“For offline tokens, Shopify returns the same refreshed token response for up to 1 hour after the original refresh, so a refresh interrupted by a transient outage can be safely retried once connectivity is restored. After this window, the previous refresh_token is invalid and can’t be used again.”
Confused about subject_token for token exchange vs refresh (expired offline access token)
I’m implementing the new expiring offline access token flow and I’m confused about how to correctly use subject_token.
I originally obtained an expiring offline access token and a refresh token. Later, when that offline access token expired, I tried to call the token exchange endpoint using the expired access token as subject_token, and got this error:
Token exchange cannot be performed due to an invalid subject token.
From the docs, I understand:
For initial token exchange, subject_token should be the session token (ID token).
For migration from a non-expiring offline token to an expiring one, subject_token should be the non-expiring offline access token.
For refreshing an expiring offline token using a refresh token, I should not use subject_token at all, but instead use a refresh grant with refresh_token.
My questions:
Once my expiring offline access token has expired, can I ever use it as subject_token in token exchange? (I assume no, based on the error.)
If the merchant logged in a month ago, how can I get a current session token (ID token) to use as subject_token?
Is it correct that I cannot retrieve a valid session token from my database and must instead wait until the merchant opens the embedded app again so Shopify issues a new session token?
In practice, for long-running/background jobs, is the correct approach:
Store the offline access token + refresh token, and
Use the refresh token flow (not token exchange) to keep the offline access token valid,
And only fall back to token exchange with a new session token when the refresh token has expired?
I want to confirm that I should never rely on old session tokens from my DB and that the only long-lived credentials I should persist are the offline access token and refresh token.