Recovery of Access Tokens for Stores Not Yet Migrated to Expiring Offline Tokens

Hi everyone,

We recently migrated one of our Shopify apps to expiring offline access tokens. We have implemented the required changes and are currently migrating existing stores in batches.

After deploying these changes, we started experiencing an issue with a few stores that have not yet been migrated to expiring offline tokens. For these stores, the existing access tokens are failing with an “Invalid API key” error, even though the stores have not yet gone through our migration process.

We are trying to understand the expected behavior during the migration period and, specifically, whether an existing offline access token can become invalid before the store has been explicitly migrated to the new expiring-token flow.

Our current approach is:

  1. Deploy the expiring offline token changes.
  2. Migrate existing stores in batches.
  3. Test the batch migration with development stores first.
  4. Gradually roll out the migration to all existing stores.

However, we are concerned about stores that are still waiting in the migration queue and suddenly start receiving “Invalid API key” responses.

Has anyone experienced a similar issue when migrating an existing app to expiring offline tokens?

Also, would Shopify recommend migrating all existing stores first, rather than performing a gradual/batched migration? If a store’s existing access token becomes invalid before it is migrated, what is the recommended way to recover or reauthorize the access token for that store?

Any guidance on the expected token behavior during the transition and the recommended migration strategy would be greatly appreciated.

Thanks!

Worth double checking one thing: expiringOfflineAccessTokens is an app-wide config flag, not something you migrate per store. Once you deploy it, it applies to every shop, there’s no way to batch it by store on Shopify’s side. Docs say existing non-expiring tokens should stay valid until they naturally cycle though, so an immediate Invalid API key right after deploy sounds like it’s coming from something else in the same release, worth checking if a scope or redirect URI changed at the same time.

We did some additional investigation and found a few more details that may help narrow down the issue.

  • We have not made any changes to the app scopes or redirect URI as part of this migration.

  • We verified the existing authentication flow in both our staging and local environments, and the old access_token continues to work as expected.

  • We also tested the migration flow using Token Exchange. Once a store is migrated through Token Exchange, the new token flow works as expected.

  • For stores that have not yet been migrated, the existing access_token continues to work normally in our testing.

  • The issue appears to be isolated to only 3 stores out of 10,000+ active stores.

  • We have not been able to reproduce the issue consistently in our staging or local environments.

  • The affected stores are returning “Invalid API key” when requests are made using their existing access token, even though they have not yet been migrated to the expiring offline token flow.

Based on these findings, it doesn’t appear to be related to a general issue with the migration code, app scopes, or redirect URI configuration.

We are trying to understand whether there is any store-specific condition or Shopify-side behavior that could cause an existing offline access token to become invalid before the store has been migrated through Token Exchange.

As I mentioned in the replied I shared earlier, I’ve also migrated an almost 5 app to expiring offline tokens. In my experience, you can migrate stores in batches and control how many stores you migrate at a time.

But as @Mubashir_Hassan mentioned, expiringOfflineAccessTokens is an app-wide configuration, not store-specific.

So if you enable the new expiring-token flow, your app needs to handle migrated and non-migrated stores differently. If you don’t handle this separately, when a non-migrated merchant opens the app, the authentication flow may automatically follow the new expiring-token flow, while you still have the old access token stored for that shop. This could cause the old token to stop working as expected.

This may be worth checking for those 3 affected stores.

If you’re doing in Ruby and need any help with the implementation, feel free to ping me. I’d be happy to help.

We did some further investigation today and found a few additional observations.

We tested this scenario with one of our development stores. When a session is generated, the store is automatically migrated to Shopify’s new expiring offline token flow, and a refresh token is generated.

However, we also found some development stores where no refresh token has been generated, while the existing/old access_token continues to work as expected.

One particularly interesting case we observed today was with a production store:

  • The existing API request was returning Invalid API key.

  • We then migrated the store to Shopify’s expiring offline token flow using the same existing token.

  • The migration was successful, and subsequent API requests worked correctly with the newly generated token.

  • Based on our understanding, if the existing token was genuinely invalid for Token Exchange, we would have expected the migration to fail with an Invalid Subject Token response rather than successfully exchanging the token.

This makes us wonder whether there is some difference in how Shopify is validating or handling existing access tokens for these specific stores.

Initially, we had a mixed state where some stores were still using the existing offline access tokens while others had already been migrated to expiring offline tokens. Supporting and troubleshooting both flows simultaneously was becoming increasingly complex.

To avoid this inconsistent state and reduce the risk of further token-related issues, we decided to migrate all existing stores to Shopify’s expiring offline token flow.

We would still appreciate any insight into why the original access token could return Invalid API key for a store that had not yet been migrated, while the same token could subsequently be used successfully to migrate that store to the expiring offline token flow.

Any explanation of the expected behavior here would be very helpful.

@jaimina good find, if token exchange accepted that same token as valid, the token itself wasn’t the problem, so the Invalid API key on the normal call is coming from something above token validation. I’d grab a request id from one of those failing calls and take it to Partner support, that’s really the only way to see what Shopify’s edge saw on that specific request.

The whole offline token thing has been so poorly handled by Shopify, it’s insane. The communication has been absolutely terrible.

I have a bunch of apps, which each have a bunch of users stuck in a sort of un-authed state because somewhere in the process failed, and Shopify revoke the old token at the very beginning of the process of migrating :man_facepalming:. The process is too flaky, it depends on too much working at the same time. Shopify even acknowledge this is something that could happen.

I created a thread recently to ask Shopify to look into this and provide a way for us to fix stores with broken authentication. So far it’s only had 1 response from Shopify.

The expiring tokens isn’t a store wide config, you can have some stores migrated but others not and we’ve ran this configuration whilst migrating too. Stores that are on non expiring tokens do not need to be refreshed, and even if you use a session token exchange, omitting the expiring flag will return the same (non expiring) access token.

Do you have a flow setup which does a session token exchange? if your codebase has the expiring token flag set for all users, then a user opening the app may trigger this depending on how you have it implemented and cause the migration to occur.

I agree with this. We did extensive testing in staging for around two weeks with 10+ stores, and everything worked as expected.

However, after deploying to production, we started seeing several unexpected issues, including issues with stores that had not yet been migrated to expiring offline tokens.

We also have a couple of stores that are now stuck with stale/invalid API keys, and we currently don’t have a way to rotate or recover those credentials because the merchants are not responding to our emails.

This has made the production migration significantly more challenging than what we observed during our staging testing.

Given these issues, I would not recommend migrating any of our other apps to expiring offline tokens until the migration process is more mature and these edge cases are properly addressed. We should first have a reliable recovery/rotation mechanism for stores with stale or invalid tokens before proceeding with further migrations.

Yes, we do have a flow where Token Exchange is enabled only for a subset of stores. We intentionally control which stores are migrated rather than enabling it for everyone.

For stores where Token Exchange is triggered through a session, we verified that the new access_token and refresh_token are being generated and safely updated in our database as expected.

The issue we are seeing is specifically with stores that have not been migrated to the expiring token flow and have not accessed the app through the Shopify Admin, meaning no new session was generated and the Token Exchange flow was never triggered for those stores.

For these stores, we are seeing the existing/old access_token return Invalid API key, even though the store has not gone through the Token Exchange migration.

That’s the part we are trying to understand, as there doesn’t appear to be any session or Token Exchange activity that should have triggered a migration for these stores.