{"record":{"id":"5e12c7f91047f027","repo":"dubinc/dub","slug":"data-error-message","errorCode":null,"errorMessage":"data.error.message","messagePattern":"data\\.error\\.message","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/stripe-app/src/utils/oauth.ts","lineNumber":60,"sourceCode":"  try {\n    const response = await fetch(`${DUB_API_HOST}/oauth/token`, {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/x-www-form-urlencoded\",\n      },\n      body: new URLSearchParams({\n        client_id: DUB_CLIENT_ID,\n        redirect_uri: getRedirectUrl(mode),\n        grant_type: \"authorization_code\",\n        code_verifier: verifier,\n        code,\n      }),\n    });\n\n    const data = await response.json();\n\n    if (!response.ok) {\n      throw new Error(data.error.message);\n    }\n\n    return data as Token;\n  } catch (e) {\n    console.error(\"Unable to retrieve Dub access token:\", (e as Error).message);\n  }\n}\n\n// Returns the user info from Dub using the access token\nexport async function getUserInfo({ token }: { token: Token }) {\n  const response = await fetch(`${DUB_API_HOST}/oauth/userinfo`, {\n    headers: {\n      Authorization: `Bearer ${token.access_token}`,\n    },\n  });\n\n  const data = await response.json();\n","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/dubinc/dub/blob/f216b94a24ca5a0a48c6543ee10392c9006c8b75/packages/stripe-app/src/utils/oauth.ts#L42-L78","documentation":"getToken exchanges the Stripe account's OAuth code with Dub for an access token; on a non-OK response it throws with the message from the OAuth provider's error payload. Note the outer catch only logs the message (\"Unable to retrieve Dub access token: ...\") and swallows the error, returning undefined to callers.","triggerScenarios":"The Dub OAuth token endpoint returns 4xx/5xx during the Stripe App install/connect flow: invalid or already-consumed authorization `code`, client id/secret mismatch, or redirect_uri mismatch.","commonSituations":"User refreshes the connect page causing code reuse; DUB_CLIENT_SECRET env misconfigured in the Stripe App deployment; redirect URI registered in Dub dev portal doesn't match the deployed one.","solutions":["Check the logged message (\"Unable to retrieve Dub access token: ...\") for the provider's error (e.g. invalid_grant).","Have the user restart the connect flow to get a fresh authorization code (codes are single-use).","Verify DUB_CLIENT_ID / DUB_CLIENT_SECRET and the registered redirect URI match the deployed Stripe App.","Fix the code path so getToken's throw isn't swallowed and callers handle undefined tokens."],"exampleFix":"// before\n} catch (e) { console.error(\"Unable to retrieve Dub access token:\", (e as Error).message); }\n// after\n} catch (e) { console.error(...); throw e; } // propagate so caller can prompt reconnect","handlingStrategy":"try-catch","validationCode":"if (!process.env.DUB_CLIENT_ID || !process.env.DUB_CLIENT_SECRET) {\n  throw new Error(\"DUB_CLIENT_ID and DUB_CLIENT_SECRET must be configured\");\n}\nif (!code || typeof code !== \"string\") throw new Error(\"OAuth code missing from connect callback\");","typeGuard":"function isTokenResponse(data: unknown): data is { access_token: string; refresh_token: string } {\n  return typeof data === \"object\" && data !== null && \"access_token\" in data;\n}","tryCatchPattern":"try {\n  const token = await getToken({ code });\n  if (!token) throw new Error(\"token exchange returned nothing — check logs for the underlying error\");\n  await saveSecret({ stripe, name: \"dub_token\", value: token });\n} catch (e) {\n  console.error(\"Dub connect failed:\", (e as Error).message);\n  return showReconnectUi();\n}","preventionTips":["Never reuse authorization codes — always start a fresh connect flow.","Verify redirect URI in Dub's OAuth app matches the deployed Stripe App.","Keep client credentials in env/secrets, rotated in sync with deployments.","Change getToken's catch to rethrow so failures are observable."],"tags":["oauth","stripe","authentication"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"f216b94a24ca5a0a48c6543ee10392c9006c8b75","analyzedAt":"2026-08-31T18:35:50.395Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}