{"record":{"id":"b1bb2a673166eff0","repo":"clockworklabs/SpacetimeDB","slug":"failed-to-verify-token-response-statustext","errorCode":null,"errorMessage":"Failed to verify token: ${response.statusText}","messagePattern":"Failed to verify token: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/bindings-typescript/src/sdk/ws.ts","lineNumber":95,"sourceCode":"}: WebSocketArgs): Promise<WebSocket> {\n  const headers = new Headers();\n\n  const WS = await resolveWS();\n\n  // We swap our original token to a shorter-lived token\n  // to avoid sending the original via query params.\n  let temporaryAuthToken: string | undefined;\n  if (authToken) {\n    headers.set('Authorization', `Bearer ${authToken}`);\n    const tokenUrl = new URL('v1/identity/websocket-token', url);\n    tokenUrl.protocol = url.protocol === 'wss:' ? 'https:' : 'http:';\n\n    const response = await fetch(tokenUrl, { method: 'POST', headers });\n    if (response.ok) {\n      const { token } = await response.json();\n      temporaryAuthToken = token;\n    } else {\n      throw new Error(`Failed to verify token: ${response.statusText}`);\n    }\n  }\n\n  const databaseUrl = new URL(`v1/database/${nameOrAddress}/subscribe`, url);\n  if (temporaryAuthToken) {\n    databaseUrl.searchParams.set('token', temporaryAuthToken);\n  }\n  databaseUrl.searchParams.set(\n    'compression',\n    { gzip: 'Gzip', brotli: 'Brotli', none: 'None' }[compression] ?? 'None'\n  );\n  if (lightMode) {\n    databaseUrl.searchParams.set('light', 'true');\n  }\n  if (confirmedReads !== undefined) {\n    databaseUrl.searchParams.set('confirmed', confirmedReads.toString());\n  }\n","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings-typescript/src/sdk/ws.ts#L77-L113","documentation":"When a DbConnection is built with an auth token, the SDK first POSTs to v1/identity/websocket-token with that token as a Bearer header, exchanging the long-lived provider token for a short-lived SpacetimeDB token (so the original never travels in the subscribe URL's query string). Any non-2xx response from that exchange is surfaced as 'Failed to verify token: <statusText>'.","triggerScenarios":"DbConnection.builder().withUri(...).withAuthToken(jwt).build() where the POST to http(s)://host/v1/identity/websocket-token returns 401/403 (token invalid, expired, wrong issuer, or not verifiable with the server's configured JWT keys), 404 (URI is not a SpacetimeDB host), or a 5xx.","commonSituations":"Reusing a stale JWT cached in localStorage; server started without the JWT validation keys matching the token's issuer; pointing the client at the wrong host or wrong ws/wss scheme (the exchange upgrades ws->http and wss->https on the same host); clock skew making a fresh token appear expired.","solutions":["Decode the JWT client-side and confirm it is well-formed and unexpired (check the exp claim) before connecting","Verify the URI host/scheme: a wrong URL makes the token exchange hit a 404/405 from a non-SpacetimeDB endpoint","Check the server's identity/JWT configuration (e.g. its public-key settings) accepts your token's issuer and signing keys","Refresh the token from your identity provider and build the connection again","Reproduce outside the SDK: curl -X POST -H 'Authorization: Bearer <token>' https://host/v1/identity/websocket-token"],"exampleFix":"// before\nconst db = DbConnection.builder()\n  .withUri('ws://localhost:3000')\n  .withAuthToken(maybeStaleToken) // may be expired\n  .build();\n\n// after: refresh, then connect\nconst token = await provider.getValidToken(); // renews when exp is near\nconst db = DbConnection.builder()\n  .withUri('ws://localhost:3000')\n  .withAuthToken(token)\n  .build();","handlingStrategy":"validation","validationCode":"function isJwtProbablyValid(token: string): boolean {\n  try {\n    const payload = JSON.parse(\n      atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'))\n    );\n    return typeof payload.exp === 'number' && payload.exp * 1000 > Date.now() + 30_000;\n  } catch {\n    return false;\n  }\n}\n// before connecting:\nif (authToken && !isJwtProbablyValid(authToken)) authToken = await provider.refresh();","typeGuard":null,"tryCatchPattern":"Wrap DbConnection.builder()...build() in try/catch; on a message matching /^Failed to verify token:/ refresh the token once and retry the connection; on a second failure surface a re-login prompt instead of retrying in a loop.","preventionTips":["Refresh tokens proactively before expiry instead of reusing cached ones","Keep the auth provider's issuer/keys in sync with the server's identity configuration","Verify connectivity to /v1identity endpoints (health check) before building connections"],"tags":["authentication","websocket","jwt","network"],"backgroundTag":"auth-token-rejected","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}