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
- 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
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
- 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
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
- JWT missing or invalid 'sub' claim
- JWT missing or invalid 'iss' claim
- Brotli compression is not supported by the runtime. Please c
- Unexpected Compression Algorithm. Please use `gzip` or `none
- v3 websocket payloads must contain at least one message
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/b1bb2a673166eff0.
Report an issue: GitHub.