{"record":{"id":"a9a3f9975be67de6","repo":"calcom/cal.diy","slug":"error-refreshing-dub-token-res-error-message","errorCode":null,"errorMessage":"Error refreshing dub token: ${res?.error?.message ?? response.statusText}","messagePattern":"Error refreshing dub token: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/app-store/dub/lib/AnalyticsService.ts","lineNumber":87,"sourceCode":"              grant_type: \"refresh_token\",\n              refresh_token: refreshToken,\n            }).toString(),\n            headers: {\n              \"Content-Type\": \"application/x-www-form-urlencoded\",\n            },\n          });\n\n          if (!response.ok) {\n            const res = await response.json();\n            if (response.status === 401) {\n              await CredentialRepository.updateCredentialById({\n                id: this.credential.id,\n                data: {\n                  invalid: true,\n                },\n              });\n            }\n            throw new Error(`Error refreshing dub token: ${res?.error?.message ?? response.statusText}`);\n          }\n          return await response.json();\n        },\n        \"dub\",\n        this.credential.userId\n      );\n\n      newToken.expiry_date = Date.now() + newToken.expires_in * 1000;\n\n      await CredentialRepository.updateCredentialById({\n        id: this.credential.id,\n        data: { key: newToken as any },\n      });\n\n      return newToken;\n    } catch (err) {\n      this.log.error(err);\n      throw err;","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/packages/app-store/dub/lib/AnalyticsService.ts#L69-L105","documentation":"Thrown inside Dub AnalyticsService.refreshAccessToken when Dub's token-refresh endpoint returns a non-2xx response. The handler reads the JSON body, marks the stored Cal.com credential invalid on HTTP 401 (so the user must re-authenticate), then throws an Error whose message interpolates Dub's error.message (or response.statusText as fallback).","triggerScenarios":"POST https://api.dub.co/oauth/token with grant_type=refresh_token returns non-2xx: refresh_token expired/revoked (401), client_secret wrong (401), rate limited (429), or Dub API outage (5xx).","commonSituations":"Dub refresh token older than 30 days (Dub tokens expire); user revoked Cal.com access in Dub; client_secret rotated but app keys not updated; Dub API rate limit; transient Dub 5xx.","solutions":["On 401, prompt the user to reinstall the Dub integration (the credential is already flagged invalid by the handler).","Verify the Dub app's client_id and client_secret in /apps/dub keys are current.","For 429/5xx, retry with exponential backoff before surfacing failure.","Confirm refresh_token was the most recent one returned by Dub (using a stale token after a refresh produces this error)."],"exampleFix":"// before\nthrow new Error(`Error refreshing dub token: ${res?.error?.message ?? response.statusText}`);\n\n// after - typed error so callers can branch\nif (response.status === 401) {\n  throw new DubTokenInvalidError(`Dub refresh failed: ${res?.error?.message ?? response.statusText}`, { credentialId: this.credential.id });\n}\nthrow new Error(`Error refreshing dub token: ${res?.error?.message ?? response.statusText}`, { cause: res });","handlingStrategy":"try-catch","validationCode":"if (!token?.refresh_token) throw new Error(\"No Dub refresh token stored; reinstall Dub integration.\");","typeGuard":"const isDubToken = (t: unknown): t is { access_token: string; refresh_token: string; expires_in: number } =>\n  typeof t === \"object\" && t !== null &&\n  typeof (t as any).access_token === \"string\" &&\n  typeof (t as any).refresh_token === \"string\";","tryCatchPattern":"try {\n  await analyticsService.sendEvent(...);\n} catch (err) {\n  if (err instanceof Error && /refreshing dub token/i.test(err.message)) {\n    // credential was flagged invalid on 401; prompt reinstall\n    await notifyUserReconnectDub(credentialId);\n    return; // analytics is non-critical\n  }\n  throw err;\n}","preventionTips":["Refresh Dub tokens proactively before expiry_date rather than on-demand.","Treat Dub analytics as non-blocking so a token failure does not break bookings.","Rotate Dub client_secret and update app keys atomically."],"tags":["dub","oauth","token-refresh","analytics","credential"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}