520 during refresh_token rotation permanently orphans the offline access token

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.

Sounds like potentially a timing issue, where the expiration date on the access token isn’t exact, perhaps a bit earlier than what’s reported?

How close was the access token to expiring? Seconds, minutes?

Exactly, and I completely agree.

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.

Not seconds or minutes, it was already long expired. About 6 hours 50 minutes past expiry when the refresh that hit the 520 fired.

The timeline from our records:

  • Access token issued 2026-06-15 03:03:42 UTC, ~1h lifetime → expired 04:03:42 UTC.
  • 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.