Re-authenticating shops after a Shopify API version upgrade 

When you upgrade your Shopify connection from an older API version to 2026-04 or later, Shopify changes how access tokens are issued. Each merchant who already has your app installed must open the embedded app once before Gadget can fetch a fresh token for their shop. Until they do, Shopify API calls made on behalf of that shop — including those triggered by webhooks — may start failing once Shopify rejects the old token.

This guide explains why this is necessary, when it applies, what merchants experience, and what you can do as a developer.

When this applies 

This requirement comes from Shopify API version 2026-04, which introduced expiring offline access tokens for public apps.

Before 2026-04, Shopify issued non-expiring offline access tokens that Gadget could store and use indefinitely. Starting with 2026-04, public apps receive short-lived access tokens (currently 60 minutes) along with longer-lived refresh tokens (Shopify currently issues these with multi-week to multi-month lifetimes). Gadget uses the refresh token to mint new access tokens automatically — but the very first refresh token for each shop can only be obtained from Shopify by going through the token-exchange flow, which only happens when a merchant opens the embedded app.

If your upgrade doesn't change the token format (most upgrades don't — the API version changelog lists what each version changes), you don't need to worry about this.

What happens during the upgrade 

  1. You change the API version on your Shopify connection in Gadget.
  2. Existing shops keep using their old access token for as long as Shopify will accept it. Until each merchant visits the embedded app, Gadget cannot upgrade them to the new token format.
  3. The first time each merchant opens the embedded app in their Shopify admin after the upgrade, Gadget runs a token-exchange request to Shopify. Shopify returns a fresh access token (and, for 2026-04+, a refresh token). Gadget stores these on the merchant's shopifyShop record and uses them from that point on.
  4. From then on, refreshes are automatic. When a stored access token is within 2 minutes of expiring, Gadget exchanges the refresh token for a new pair on the next embedded-app visit or userland API call. As long as the merchant uses the app at least once before the refresh token itself expires, they don't need to do anything.

If a merchant goes inactive for longer than the refresh token's lifetime, the refresh token also expires and they'll need to open the embedded app again to mint a new pair — same as the initial post-upgrade case.

What merchants experience 

Merchants don't see anything special. They open your app the way they normally would — by going to Apps in their Shopify admin and clicking your app's tile, or by using any deep link to the embedded app — and the upgrade happens transparently in the background. There's no confirmation dialog, no permissions prompt, and no extra step for them to take.

What happens if a merchant doesn't open the app 

If a merchant has installed your app but never opens it after the upgrade, Gadget will keep using their old access token until Shopify rejects it. Once Shopify starts rejecting the old token:

  • Webhook-triggered actions that call the Shopify Admin API will fail with 401 Unauthorized or 403 Forbidden (the latter with a "non-expiring access tokens are no longer accepted" message). Action code that doesn't touch Shopify — for example, a webhook handler that only updates Gadget's database — still runs successfully.
  • Background syncs and reconciliation for that shop will fail. Gadget's sync-side auth-error handling marks the shop as uninstalled when these failures are persistent; webhook-triggered failures don't currently trigger that path, so a shop in this state will keep receiving (and partially failing) webhook traffic until the merchant returns.
  • The merchant won't see any errors in the embedded app until they next open it — at which point Gadget completes the token exchange and everything resumes working.

No data is lost. Webhooks that arrive while the shop is in this state are queued through Gadget's normal retry and reconciliation machinery, and re-run successfully once the merchant opens the app and tokens are refreshed.

How to minimize the impact 

The token exchange must originate from a Shopify session token, and Shopify only issues those when a merchant actually opens the embedded app — there's no Gadget-side API to force a refresh. The most effective ways to reduce the window in which shops are stuck:

  • Email merchants a deep link to the embedded app. A link like https://admin.shopify.com/store/<shop-handle>/apps/<app-handle> opens the app in one click and triggers the token exchange immediately. This is by far the highest-leverage action for a large install base — you can pull the list of affected shops by querying shopifyShop records that are missing a refreshToken (see below).
  • Time the upgrade to coincide with a release of new features they'll want to try in the embedded app, so merchants are naturally pulled in.
  • Deploy during low-traffic windows so the window in which webhooks may be processed without a fresh token is shorter.

How to find stuck shops 

After an upgrade to 2026-04+, you can identify shops that haven't been re-authenticated yet by querying the shopifyShop model for installed shops with no refreshToken:

GraphQL
query StuckShops { shopifyShops( filter: { state: { matches: { created: "installed" } } refreshToken: { isSet: false } } ) { edges { node { id myshopifyDomain email } } } }

Shops returned here are still on the legacy non-expiring token. Once a merchant opens the embedded app, refreshToken and accessTokenExpiresAt populate, and the shop drops off this list.

Recovering a stuck shop 

To unblock a single stuck shop, have the merchant open the embedded app in their Shopify admin (or click any deep link to it). The first session-token-bearing request after they land triggers the token exchange, populates accessToken, refreshToken, and accessTokenExpiresAt on the shopifyShop record, and resumes normal API access. Webhooks queued during the stuck period are picked up by reconciliation on the next nightly run.

If the shop was uninstalled and reinstalled in the meantime, Shopify's app/installed webhook handles the recovery automatically — no merchant action is required.

Was this page helpful?