Shopify OAuth refresh token invalid_grant — What it means & how to fix it
How to diagnose and fix Shopify refresh token invalid_grant errors
Building Shopify apps with OAuth 2.0? Refresh token failures are unavoidable. They typically appear as invalid_grant, interrupting order processing, inventory syncs, or automated store operations.
This guide walks you through diagnosing and fixing Shopify refresh token errors, plus production-ready practices that minimize token lifecycle headaches (including how to sidestep refresh token race conditions and rotation gotchas). For broader context, see API Auth Is Deeper Than It Looks and Why OAuth Is Still Hard.
Spot the error
When your backend POSTs to Shopify's token endpoint to swap a refresh token for a new access token:
- Token endpoint:
https://{shop}.myshopify.com/admin/oauth/access_token - Common HTTP status:
400 - Common OAuth error:
invalid_grant
Example response payload:
Shopify may also include an error_description field with additional context. Either way, the meaning is consistent: the refresh token you have stored can't be used to mint a new access token. Retrying the same request will keep failing until you address the underlying cause.
Why did Shopify reject the refresh token?
Shopify returns invalid_grant when the refresh token you're using can't be exchanged for a new access token. This happens because the token is invalid, expired, revoked, or you're using a stale token after rotation. The key is to treat invalid_grant as a token lifecycle problem, not as a transient network issue.
There are several recurring root causes:
Most common: You're not using the latest refresh token
Some OAuth providers rotate refresh tokens. Shopify does: when a refresh token is exchanged, the previous access and refresh tokens are invalidated and new tokens are returned. If you keep refreshing with an older token, your next refresh can fail with invalid_grant.
This is also the most common failure mode when teams run token refreshes in multiple processes/containers without proper locking: one process stores the "new" refresh token, another process overwrites it with the stale one, and suddenly every refresh starts failing.
Refresh token expired due to inactivity
Shopify refresh tokens expire after 90 days for expiring offline tokens (introduced December 2025). If a refresh token isn't used within that window, the authorization is effectively revoked and the merchant must re-authorize. This shows up as invalid_grant on refresh.
Access token expired before refresh
Shopify access tokens expire in 1 hour (3600 seconds) for expiring offline tokens. If you miss the refresh window or the access token expires before you refresh, you'll need to use the refresh token. However, if the refresh token itself has expired due to inactivity, Shopify returns invalid_grant and requires re-authorization.
App uninstalled or merchant revoked access
If a merchant uninstalls your app from their Shopify store or revokes access in their app settings, Shopify immediately invalidates all associated tokens. This can happen through:
- Uninstalling the app from the Shopify admin
- Manually revoking access in app settings
- Store admin removing the app from the store
Refresh request rejected → token revoked
Shopify may revoke refresh tokens when a refresh request is rejected (for example, when the server returns 400 or 401). Common reasons:
- The refresh token is stale because it was already rotated
- The refresh token was retried after expiration
- The request used wrong client credentials (often after a client secret rotation)
Scope changes or app configuration updates
If the OAuth app's scopes are modified after the initial authorization, or if the app configuration changes in Shopify, existing refresh tokens may become invalid and require re-authorization.
Client credentials mismatch
Using incorrect client_id or client_secret when refreshing tokens will result in invalid_grant errors. This often happens after:
- Client secret rotation
- Using credentials from the wrong Shopify app
- Copy-paste errors in environment variables
Refresh-token concurrency bugs (race conditions)
Refresh tokens become tricky at scale because token refresh is triggered by many events:
- Scheduled inventory syncs
- Webhook deliveries
- User-triggered "sync now" actions
- Background retries after 401s
If two workers refresh the same Shopify connection at the same time, you can get a race:
- Worker A refreshes and receives a new refresh token
- Worker B refreshes with the now-stale token and gets
invalid_grant(or worse: succeeds later and overwrites state)
This class of bugs is subtle, sporadic, and highly load-dependent. If this sounds familiar, see our deep dive on OAuth token refresh concurrency.
Security heuristics and automatic revocations
Shopify may revoke refresh tokens based on security heuristics or policy changes. This can include:
- Unusual access patterns detected by Shopify's systems
- Suspicious activity or potential security threats
- Rate limit violations or abuse detection
- Token exposure or compromise detected
How to fix it
1. Confirm you're using the latest refresh token
If Shopify returns a new refresh_token on refresh, always persist it and use that value for the next refresh. Shopify invalidates previous refresh tokens as soon as a new one is issued (one-time use).
Fix:
- Store the latest
refresh_tokenreturned by Shopify every time you refresh - Update the stored refresh token immediately after each refresh (see concurrency section below)
2. Verify your refresh request is correct
Double-check the basics:
grant_type=refresh_token- Content-Type is
application/x-www-form-urlencoded - Authorization uses Basic Auth (
client_id:client_secretbase64-encoded) or includes credentials in the request body - You're hitting the correct token endpoint:
https://{shop}.myshopify.com/admin/oauth/access_token
3. Check if merchant revoked access
The most common cause is that the merchant manually uninstalled the app or revoked access. There's no way to revive a revoked token.
4. Eliminate refresh concurrency (single-flight + locking)
If you run multiple workers, you need to treat "refresh token per connection" as a shared resource.
At minimum, ensure:
- Only one refresh can run for a given connection at a time (distributed lock or single-flight)
- Other requests wait for the in-flight refresh and then use the newly stored access token
- Updates to
(access_token, refresh_token, expires_at)happen atomically
Rather not build this yourself? Nango (open-source OAuth) includes refresh concurrency controls. Your app just needs to fetch the latest access token before each Shopify API call.
5. If it's truly invalid/expired/revoked: trigger re-auth (don't keep retrying)
After you've ruled out rotation and credential issues, treat invalid_grant as a terminal state for that connection.
Practical production playbook:
- Retry once to cover rare partial failures
- If it fails again, mark the connection as "needs re-auth"
- Pause background syncs for that connection
- Ask the merchant to reinstall the app or reconnect Shopify in-product
This keeps retries quiet, avoids wasteful refresh loops, and gives merchants a clear next step.
How to prevent Shopify refresh token issues
Use this checklist to reduce "why did my Shopify integration break?" tickets:
- Refresh proactively before expiration
Shopify access tokens expire in 1 hour for expiring offline tokens. Refresh tokens before they expire to keep connections active and avoid unexpected failures. - Persist the rotated refresh token
Treat "save the new refresh token" as mandatory after every refresh. Shopify rotates refresh tokens, and the old one becomes unusable immediately. - Discard old access tokens immediately
Cache invalidation mistakes are a frequent cause of hard-to-debug auth failures. Once you refresh, discard the old Shopify access token. - Make refresh single-flight and atomic
Ensure one refresh per connection and atomic writes of(access_token, refresh_token, expires_at). If you're unsure, read How to handle concurrency with OAuth token refreshes. - Watch
invalid_granttrends
A baseline rate is normal at scale, but spikes usually indicate rotation, concurrency, or credential issues. - Handle app uninstallation gracefully
Set up monitoring to detect when your OAuth app is uninstalled from Shopify stores so you can clean up associated data and notify merchants proactively. - Invest in a clean re-auth UX
A clear "Reconnect Shopify" flow saves days of back-and-forth with merchants and reduces support burden significantly.
Skip the headache, let Nango refresh for you
Nango's open-source API auth offers:
- 500+ pre-built OAuth flows, including full support for all Shopify APIs
- Automatic OAuth access token refreshing and rotation
- Webhooks when a refresh token is revoked, so you can warn the user instantly
- Built-in error handling for all OAuth edge cases
If you're building a Shopify API integration and you're tired of token lifecycle edge cases, Nango can run the refresh pipeline for you.
Focus on product features and let Nango handle the token lifecycle.




