Troubleshooting 401 Unauthorized and Token Errors
How to diagnose and fix "401 Unauthorized", "invalid API key", and "token not valid yet" errors when connecting to LiveKit Cloud. Covers clock skew, environment mismatches, and expired tokens.
Last Updated:
If you're seeing 401 Unauthorized, invalid API key, or token not valid yet errors when connecting to LiveKit, the problem almost always comes down to one of three things: your system clock is wrong, your API keys don't match the environment you're connecting to, or the token has already expired by the time it reaches the server.
This guide walks through each cause and how to fix it.
Background: how token validation works
LiveKit uses JSON Web Tokens (JWT) for authentication. When a client connects, the server checks three things:
- Signature — was this token signed with an API secret that matches one of my configured API keys?
nbf(not before) — is the current time after the token's start time?exp(expiration) — is the current time before the token's expiration?
If any of these checks fail, the server rejects the connection. For full details on token structure, grants, and lifecycle, see the Tokens & grants reference.
Cause 1: clock skew
Symptoms: "token not valid yet" or "token expired" errors even though the token was just created.
The nbf claim is set to the current time when the token is generated. If the LiveKit server's clock is even a few seconds behind the machine that generated the token, the server sees the token as "not valid yet." The reverse — server clock ahead of the generator — can cause a token to appear expired prematurely.
How to fix it
Check for clock skew. Compare your local clock against a public NTP server to see how far off you are. If the offset is more than a few seconds, that's likely your problem.
# Linux — shows the offset between your clock and an NTP serverntpdate -q pool.ntp.org
# macOSsntp pool.ntp.org
Synchronize your clocks with NTP. Most cloud providers handle this automatically, but self-hosted deployments and local development machines can drift.
# Check NTP status and enable synchronization (Linux)timedatectl statussudo timedatectl set-ntp true
Check container and VM time. Containers inherit time from the host. If you've suspended and resumed a laptop or VM, the clock may be stale — run the skew check above from inside the container to confirm.
Add a small nbf buffer. If you control token generation, you can set the nbf claim to a few seconds in the past to tolerate minor drift. The LiveKit server SDKs set nbf to time.Now() by default. See the custom token generation docs for examples in all supported languages.
Cause 2: API key or secret mismatch
Symptoms: "invalid API key" or signature verification errors.
This happens when the API key used to sign the token doesn't match any key the LiveKit server knows about, or when the secret doesn't match the key. The most common scenarios:
- Environment mismatch — you're generating tokens with dev keys but connecting to a production LiveKit Cloud project (or vice versa). Each LiveKit Cloud project has its own API key/secret pair.
- Copy-paste errors — trailing whitespace, newlines, or partial keys in environment variables.
- Stale environment variables — your
.envfile, container config, or secrets manager still has keys from an old or deleted project.
How to fix it
Verify which project you're targeting. Check the server URL in your connection code. LiveKit Cloud URLs follow the pattern wss://<project-slug>.livekit.cloud. Make sure the API key and secret you're using belong to that specific project. You can find the host URL, API key, and API secret for each of your projects in the LiveKit Cloud settings.
Check your environment variables. Print them (carefully, in a secure context) to confirm they're loaded correctly:
# Confirm the variables are set and not emptyecho "Key length: ${#LIVEKIT_API_KEY}"echo "Secret length: ${#LIVEKIT_API_SECRET}"
For self-hosted deployments, verify the key/secret pair in your livekit.yaml configuration matches what your token generator is using.
For guidance on structuring separate dev/staging/prod projects with isolated credentials, see the Structuring development and test environments field guide.
Cause 3: token already expired
Symptoms: "token expired" immediately after creation.
This usually means the TTL (time-to-live) was set to zero or a negative value, or a cached/stale token is being reused after it has expired.
How to fix it
- Set a positive TTL when creating tokens. A TTL of 10 minutes to 1 hour is typical for most applications.
- Don't cache tokens aggressively. If your application caches the token on the client side, ensure it requests a new one before the old one expires.
- Check your server's clock — this overlaps with the clock skew issue above.
Note that token expiration is only checked at connect time. If a token expires while a participant is already connected, the session continues — LiveKit proactively issues refreshed tokens to connected clients. See Token lifecycle for details.
Debugging a token
When you hit an auth error, the first step is to decode the token and look at the claims.
Using jwt.io: Paste your token to see the decoded header, payload, and whether the signature is valid (if you provide the secret).
Using the command line: On Mac or Linux you can decode the payload with:
# Decode the payload (second segment of the token)echo "<your-token>" | cut -d'.' -f2 | base64 -d 2>/dev/null | jq .
(On Windows, use jwt.io or another JWT decoder instead.)
When you look at the decoded payload, check:
iss— does this API key belong to the project you're connecting to?nbfandexp— convert these Unix timestamps to human-readable times and compare them to the current time on both the generating machine and the LiveKit server. JWT time validation allows at most a few minutes of leeway for clock skew; the server uses go-jose, which defaults to 1 minute. When testing locally, a wrong timezone is a common cause—the effective clock can be a full hour off (e.g. local time vs UTC or daylight saving). If the clock is off, sync it on both the token-generating machine and the LiveKit server.video.room— is this the room you're trying to join?video.roomJoin— is this set totrue?
For token creation examples in Node.js, Python, Go, Ruby, Rust, and PHP, see the custom token generation docs. For a complete reference on grants and permissions, see Tokens & grants.
Related resources
- Tokens & grants reference — full token structure, grants, permissions, and lifecycle
- Authentication overview — recommended authentication workflows and TokenSource
- Custom token generation — server-side token creation examples in all supported languages
- Structuring development and test environments — isolating credentials across dev, staging, and production
- Diagnosing connection errors — using the Connection Test utility for broader connectivity issues
- JWT.io — online JWT decoder and debugger