clockworklabs/SpacetimeDB · error · Error

Failed to verify token: ${response.statusText}

Error message

Failed to verify token: ${response.statusText}

What it means

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>'.

Source

Thrown at crates/bindings-typescript/src/sdk/ws.ts:95

}: WebSocketArgs): Promise<WebSocket> {
  const headers = new Headers();

  const WS = await resolveWS();

  // We swap our original token to a shorter-lived token
  // to avoid sending the original via query params.
  let temporaryAuthToken: string | undefined;
  if (authToken) {
    headers.set('Authorization', `Bearer ${authToken}`);
    const tokenUrl = new URL('v1/identity/websocket-token', url);
    tokenUrl.protocol = url.protocol === 'wss:' ? 'https:' : 'http:';

    const response = await fetch(tokenUrl, { method: 'POST', headers });
    if (response.ok) {
      const { token } = await response.json();
      temporaryAuthToken = token;
    } else {
      throw new Error(`Failed to verify token: ${response.statusText}`);
    }
  }

  const databaseUrl = new URL(`v1/database/${nameOrAddress}/subscribe`, url);
  if (temporaryAuthToken) {
    databaseUrl.searchParams.set('token', temporaryAuthToken);
  }
  databaseUrl.searchParams.set(
    'compression',
    { gzip: 'Gzip', brotli: 'Brotli', none: 'None' }[compression] ?? 'None'
  );
  if (lightMode) {
    databaseUrl.searchParams.set('light', 'true');
  }
  if (confirmedReads !== undefined) {
    databaseUrl.searchParams.set('confirmed', confirmedReads.toString());
  }

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Decode the JWT client-side and confirm it is well-formed and unexpired (check the exp claim) before connecting
  2. Verify the URI host/scheme: a wrong URL makes the token exchange hit a 404/405 from a non-SpacetimeDB endpoint
  3. Check the server's identity/JWT configuration (e.g. its public-key settings) accepts your token's issuer and signing keys
  4. Refresh the token from your identity provider and build the connection again
  5. Reproduce outside the SDK: curl -X POST -H 'Authorization: Bearer <token>' https://host/v1/identity/websocket-token

Example fix

// before
const db = DbConnection.builder()
  .withUri('ws://localhost:3000')
  .withAuthToken(maybeStaleToken) // may be expired
  .build();

// after: refresh, then connect
const token = await provider.getValidToken(); // renews when exp is near
const db = DbConnection.builder()
  .withUri('ws://localhost:3000')
  .withAuthToken(token)
  .build();
Defensive patterns

Strategy: validation

Validate before calling

function isJwtProbablyValid(token: string): boolean {
  try {
    const payload = JSON.parse(
      atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'))
    );
    return typeof payload.exp === 'number' && payload.exp * 1000 > Date.now() + 30_000;
  } catch {
    return false;
  }
}
// before connecting:
if (authToken && !isJwtProbablyValid(authToken)) authToken = await provider.refresh();

Try / catch

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.

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/b1bb2a673166eff0. Report an issue: GitHub.