07 · Troubleshooting
Master Error-Code Table
The WsaVerificationError.code Thrown by verifyWsa
| code | Common cause | How to troubleshoot |
|---|---|---|
InvalidSignature |
Wrong secret, tampered token, or the wrong Tier1/Tier2 secret used | Confirm the backend uses the same secret as registered/distributed; pass the per-app secret in as the raw string, do not hex-decode it |
WrongAudience |
audience does not match the token's aud |
audience must exactly equal the host of the registered URL (e.g., app.example.com), without the port/protocol |
WrongIssuer |
The issuer configuration was changed |
Keep the default gptbots-workspace |
Expired |
The token is over 5 minutes old, or the server clock is skewed | NTP-sync the server clock; the wsa is a one-time bootstrap token, do not cache and reuse it |
NotYetValid |
iat/nbf is in the future |
Usually the issuer/verifier clocks are severely out of sync |
MissingClaim |
Missing exp/sub/role/workspace_id |
A normal platform token never lacks these; if it occurs, the token's origin is suspicious |
UnsupportedAlgorithm |
alg is not in the allowlist |
By default only HS256; RS256 requires an explicit algorithms:['RS256'] + publicKey |
InvalidToken |
The token is empty/structurally invalid/truncated | Check whether the frontend correctly obtained the complete wsa and whether the URL was rewritten by an intermediate layer |
The /token Exchange Phase (Pull)
| code | Meaning | Trigger condition |
|---|---|---|
403209 |
Invalid grant | The code is missing, expired, or already used (replay). A code can only be used once |
403210 |
Invalid verifier | The PKCE codeVerifier does not match the code_challenge from initiation |
Push sign-token (Called by the Workspace Frontend, for Awareness Only)
| code | Meaning | Trigger condition |
|---|---|---|
40000 |
Parameter error | The URL is not on file, or auth_mode is not workspace_account |
40100 |
Permission deny | Not logged in or the session has expired |
40105 |
Require member of project | The clicker is not a member of this workspace |
40320 |
Member not found | The account was deactivated/deleted |
FAQ
Q: Should I choose push or pull?
A: If the entry is in the workspace "Extensions" page and you want to build an "embedded app" → push (simplest). If your app is a standalone site and you want to place a "Sign in with GPTBots" button → pull (M-Auth, more secure, the wsa never enters the browser). See 02.
Q: Can I decode the JWT directly on the frontend to get user info?
A: You can decode it to look, but you cannot treat it as a trusted identity — without the secret you cannot verify the signature, and the payload can be forged. Any authorization decision must be based on the backend's verifyWsa result. See 05 §5.
Q: What do I do when the wsa expires?
A: The wsa is just a one-time bootstrap token (5 minutes). Verify it once on landing, exchange it for your own session, and use your session thereafter — do not depend on the wsa again. The next time the user opens the app from the extensions page, they will receive a fresh wsa.
Q: The landing page's consumeHandoff throws "no handoff token present"?
A: This means the current URL has no ?wsa= — possibly the user accessed directly, or the wsa was already stripped by a previous successful exchange. Distinguish "first landing with a token" from "an ordinary visit"; for the latter, go to your own login/anonymous branch.
Q: Pull login callback reports StateMismatch / MissingRequest?
A: MissingRequest = startWorkspaceLogin was not called first in the same browser session (the PKCE verifier is stored in sessionStorage, and it is lost if you switch tabs or clear storage). StateMismatch = the callback state does not match the stored one (CSRF protection); make sure you are not going across devices/sessions.
Q: Pull login reports CryptoUnavailable?
A: PKCE requires Web Crypto, which is only available in a secure context (HTTPS or localhost). Use HTTPS or debug locally on localhost.
Q: redirect_uri reports invalid_request / returns to the organization selection page?
A: The redirect_uri's scheme + host must exactly match client_id (the registered URL). An https app cannot configure an http callback; the host must match. The platform never redirects to an unverified address, so it returns to the organization selection page with ?error=invalid_request.
Q: How do I use the per-app secret as the HMAC key? Do I need to base64/hex-decode it?
A: Pass it in as the raw string (both the SDK and the platform use the UTF-8 bytes directly as the HMAC key). A Tier 2 secret looks like wext_+64 hex; the whole string is the secret, do not decode it.
Q: Can CommonJS projects use the SDK?
A: The SDK is ESM. CJS projects should use a dynamic import() or convert the relevant modules to ESM.
Q: How do I change the icon/name?
A: Tier 2: edit it in Space Management; Tier 1: contact platform operations to change the dictionary entry.
Q: What happens if an admin disables my app?
A: An organization admin can disable an app in Space Management (including the visibility of a platform public app within this organization). After disabling, members of that organization will no longer see the entry on the extensions page, and the platform will no longer issue a wsa for that organization (both push and pull are refused).
Debugging Checklist
-
audience== the host of the registered URL (the most common source ofWrongAudience) - The backend secret matches what was registered/distributed, and is kept only on the backend
- The server clock is NTP-synced (
Expired/NotYetValidare mostly due to the clock) -
history.replaceStateto stripwsa/codeafter landing - Exchange for your own session; subsequent requests do not pass through the
wsa - Pull: an HTTPS/localhost secure context;
redirect_urisame-origin asclient_id - Multi-tenant isolation by
workspace_id
References
- The articles in this guide: 01 Quick Start · 02 Core Concepts · 03 Push · 04 Pull · 05 Verification and Security · 06 API
