{"record":{"id":"7ed595e475e891c1","repo":"calcom/cal.diy","slug":"code-must-be-a-string-7ed595","errorCode":null,"errorMessage":"`code` must be a string","messagePattern":"`code` must be a string","errorType":"http","errorClass":"HttpError","httpStatus":400,"severity":"error","filePath":"packages/app-store/dub/api/callback.ts","lineNumber":27,"sourceCode":"import createOAuthAppCredential from \"../../_utils/oauth/createOAuthAppCredential\";\nimport { decodeOAuthState } from \"../../_utils/oauth/decodeOAuthState\";\nimport { dubAppKeysSchema } from \"../lib/utils\";\n\nexport default async function handler(req: NextApiRequest, res: NextApiResponse) {\n  const { code } = req.query;\n\n  const state = decodeOAuthState(req, \"dub\");\n\n  if (typeof code !== \"string\") {\n    if (state?.onErrorReturnTo || state?.returnTo) {\n      res.redirect(\n        getSafeRedirectUrl(state.onErrorReturnTo) ??\n          getSafeRedirectUrl(state?.returnTo) ??\n          `${WEBAPP_URL}/apps/installed`\n      );\n      return;\n    }\n    throw new HttpError({ statusCode: 400, message: \"`code` must be a string\" });\n  }\n\n  if (!req.session?.user?.id) {\n    throw new HttpError({ statusCode: 401, message: \"You must be logged in to do this\" });\n  }\n\n  const { client_id, redirect_uris, client_secret } = await getParsedAppKeysFromSlug(\"dub\", dubAppKeysSchema);\n\n  const codeExchangeUrl = `https://api.dub.co/oauth/token`;\n\n  const result = await fetch(codeExchangeUrl, {\n    method: \"POST\",\n    body: new URLSearchParams({\n      code,\n      client_id,\n      redirect_uri: redirect_uris,\n      client_secret,\n      grant_type: \"authorization_code\",","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/packages/app-store/dub/api/callback.ts#L9-L45","documentation":"Thrown as an HttpError (HTTP 400) by the Dub OAuth callback when the `code` query parameter is not a string. OAuth callbacks receive code as a single string; a non-string (undefined, or string[] when duplicated) means the callback was invoked without an authorization code (user denied consent, or a malformed redirect). The handler only throws if no safe returnTo/onErrorReturnTo redirect target exists.","triggerScenarios":"Dub redirects back to /api/dub/callback with no `code` (user clicked 'Deny' on the Dub consent screen), with code as an array (?code=a&code=b), or with an error param instead of code; AND state.onErroredReturnTo / state.returnTo are also unset.","commonSituations":"User cancels the Dub OAuth consent; Dub misconfigured redirect_uri sending extra query params; integrations page opened the OAuth flow without setting a returnTo state.","solutions":["Always encode a returnTo / onErrorReturnTo in the OAuth state so denial degrades to a redirect, not a thrown 400.","Handle the OAuth `error` and `error_description` query params (e.g. access_denied) explicitly before checking code.","If code is an array, take the first element: const code = Array.isArray(req.query.code) ? req.query.code[0] : req.query.code.","Confirm the Dub app's redirect_uris matches the Cal.com callback URL exactly."],"exampleFix":"// before\nif (typeof code !== \"string\") {\n  if (state?.onErrorReturnTo || state?.returnTo) { res.redirect(...); return; }\n  throw new HttpError({ statusCode: 400, message: \"`code` must be a string\" });\n}\n\n// after - treat denial / array gracefully\nconst code = Array.isArray(req.query.code) ? req.query.code[0] : req.query.code;\nif (typeof code !== \"string\") {\n  if (req.query.error) {\n    res.redirect(`${WEBAPP_URL}/apps/installed?error=${encodeURIComponent(req.query.error)}`);\n    return;\n  }\n  res.redirect(`${WEBAPP_URL}/apps/installed`);\n  return;\n}","handlingStrategy":"validation","validationCode":"const rawCode = Array.isArray(req.query.code) ? req.query.code[0] : req.query.code;\nif (typeof rawCode !== \"string\") {\n  if (req.query.error) {\n    res.redirect(`${WEBAPP_URL}/apps/installed?error=${encodeURIComponent(String(req.query.error))}`);\n    return;\n  }\n  res.redirect(`${WEBAPP_URL}/apps/installed`);\n  return;\n}","typeGuard":"const isStringCode = (v: unknown): v is string => typeof v === \"string\" && v.length > 0;","tryCatchPattern":"if (typeof code !== \"string\") {\n  const fallback = getSafeRedirectUrl(state?.onErrorReturnTo) ?? getSafeRedirectUrl(state?.returnTo) ?? `${WEBAPP_URL}/apps/installed`;\n  res.redirect(fallback);\n  return;\n}","preventionTips":["Always encode onErrorReturnTo / returnTo in the OAuth state.","Handle the OAuth error/error_description query params explicitly.","Normalize array query params before the type check."],"tags":["dub","oauth","callback","validation","query-params"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}