medusajs/medusa · error · MedusaError
Could not exchange token, ${r.status}, ${r.statusText}
Error message
Could not exchange token, ${r.status}, ${r.statusText} What it means
During the Github OAuth callback, the provider exchanges the authorization code for an access token at Github's token endpoint. If Github returns a non-2xx response, the provider throws INVALID_DATA with the HTTP status and reason.
Source
Thrown at packages/modules/providers/auth-github/src/services/github.ts:119
if (!state) {
return { success: false, error: "No state provided, or session expired" }
}
const params = `client_id=${this.config_.clientId}&client_secret=${this.config_.clientSecret}&code=${code}&redirect_uri=${state.callback_url}`
const exchangeTokenUrl = new URL(
`https://github.com/login/oauth/access_token?${params}`
)
try {
const response = await fetch(exchangeTokenUrl.toString(), {
method: "POST",
headers: {
Accept: "application/json",
},
}).then((r) => {
if (!r.ok) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Could not exchange token, ${r.status}, ${r.statusText}`
)
}
return r.json()
})
const providerMetadata = {
access_token: response.access_token,
refresh_token: response.refresh_token,
// The response is in seconds
access_token_expires_at: new Date(
Date.now() + response.expires_in * 1000
).toISOString(),
refresh_token_expires_at: new Date(
Date.now() + response.refresh_token_expires_in * 1000
).toISOString(),View on GitHub (pinned to 5e06e544a2)
Solutions
- Verify clientSecret and callbackUrl in the provider options exactly match the Github OAuth app settings
- Retry the full OAuth flow from the beginning to get a fresh authorization code (codes are single-use and short-lived)
- Check Github's status page if exchanges fail consistently
Defensive patterns
Strategy: retry
Validate before calling
if (!req.query.code) {
return res.redirect('/auth/github/github') // restart flow instead of exchanging a dead code
} Try / catch
try { await provider.validateCallback(req.query) } catch (e) { if (e.type === 'invalid_data') res.redirect(loginUrl) else throw e } // restart flow on exchange failure Prevention
- Never allow the callback URL to be refreshed/replayed
- Keep clientSecret and callbackUrl in sync with the Github app
- Handle 'exchange failed' by restarting the OAuth flow, not by retrying the same code
When it happens
Trigger: The fetch to `https://github.com/login/oauth/access_token` failing: expired/reused authorization code, mismatched client secret, or mismatched redirect_uri.
Common situations: Replaying or refreshing a callback URL after the one-time code was consumed, wrong clientSecret in config, or callbackUrl differing from the OAuth app's registered callback URL.
Related errors
- Could not exchange token, ${r.status}, ${r.statusText}
- Github clientId is required
- Github clientSecret is required
- Github callbackUrl is required
- Github does not support registration. Use method `authentica
AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27).
Data as JSON: /api/errors/c65565ea7e1f1bf6.
Report an issue: GitHub.