JWT validation fails and unclear documentation
# support
e
I've asked a few weeks ago about JWT creation for the webclient, it still seems to be broken. Aside from that the docs seem to have changed and they are now linking to an non existing page. https://docs.vapi.ai/customization/jwt-authentication The code:
Copy code
ts
        const payload = {
          orgId: process.env.VAPI_ORG_ID,
          // This is the scope of the token
          token: {
            tag: 'public',
            restrictions: {
              enabled: process.env.NODE_ENV !== 'development',
              allowedOrigins: [process.env.NEXT_PUBLIC_APP_URL],
              allowedAssistantIds: [assistant.vapiId],
              allowTransientAssistant: false,
            },
          },
        }
        // Get the private key from environment variables
        const key = new TextEncoder().encode(process.env.VAPI_PRIVATE_KEY)

        // Generate the token using a JWT library or built-in functionality
        const token = await new SignJWT(payload)
          .setProtectedHeader({ alg: 'HS256' })
          .setExpirationTime('1h')
          .sign(key)
The JWT validates correctly using the private key, however from the vapi api we receive:
Copy code
{
  "message": "Couldn't verify JWT.",
  "error": "Unauthorized",
  "statusCode": 401
}
It would really help if the docs were complete and include working demo's or examples.
v
Based on your error message, this is likely a key mismatch issue. Try verifying your private key:
Copy code
ts
// Make sure you're using the correct private key format
// Vapi might be expecting a different format than what you're providing
const key = process.env.VAPI_PRIVATE_KEY
  .replace(/\\n/g, '\n')  // Handle any escaped newlines
  .trim();

// Then use it without TextEncoder
const token = await new SignJWT(payload)
  .setProtectedHeader({ alg: 'HS256' })
  .setExpirationTime('1h')
  .sign(Buffer.from(key, 'utf-8'))  // Or appropriate format
You can also verify your JWT at [jwt.io](https://jwt.io) to ensure it's correctly formatted. Sources: - [Discord message about JWT validation failures](https://discord.com/channels/1211482211119796234/1336360744916226168) - [Discord message about troubleshooting JWT issues](https://discord.com/channels/1211482211119796234/1336414771309838448)
k
The 401 error may be due to an incorrect authorization header format, expired token, or signature mismatch, ensure your JWT is correctly formatted with the Bearer prefix, not expired, and signed with the correct key and algorithm..
e
I checked all that ofcourse, the payload is signed correctly. The algorithm is correct i believe, but like i said the docs are missing all this information
d
Same issue here - would be great to have an update. thanks
c
If you’re still getting a 401 error despite a correct JWT, ensure your Authorization header uses the exact Bearer token format, claims like iss and aud match Vapi’s expectations, the algorithm and signing key are correct, your server’s clock is synced. If you follow those instructions and still face issues, feel free to reach out.
g
getting the same issue, funny thing is it was working until 2 days ago then suddenly stopped + the jwt docs are gone so no clue how to fix it either
c
Sorry about the confusion with the documentation link; it seems to be broken. Please use the direct documentation for generating and validating JWTs for your setup. Make sure your JWT payload includes a
token
object and the correct scope (
public
or
private
), as missing scopes can lead to issues. If you continue experiencing issues, check your environment variables to ensure
ORG_ID
and
PRIVATE_KEY
are accurately set. If you need any more help, feel free to ask.
e
Thanks for fixing the link to the docs, however after over one month of trying to get this to work, i still don't have a solution. Tried different JWT libraries, all same result... The JWT is correct and can be verified using the orgs private key. Please just add a fully working example into the docs or add a jwt method into the node client to resolve this once and for all. https://cdn.discordapp.com/attachments/1369341622793535508/1374750169584894073/Screenshot_2025-05-21_at_15.02.47.png?ex=682f2f5e&is=682dddde&hm=d7ea37fbc690bc05a6a20ba97d8b2ab9246aca7d934acfc8663cdf992966c147&
@Kings_bigđź’«
c
Copy code
const jwt = require('jsonwebtoken'); // Switch to jsonwebtoken

// Clean any potential formatting issues in the key
const privateKey = process.env.VAPI_PRIVATE_KEY.trim(); 

const payload = {
  orgId: process.env.VAPI_ORG_ID,
  token: {
    tag: 'public',
    restrictions: {
      enabled: true,
      allowedOrigins: [process.env.NEXT_PUBLIC_APP_URL],
      allowedAssistantIds: [assistant.vapiId],
      allowTransientAssistant: false,
    },
  }
};

// Use same library/approach as Vapi backend
const token = jwt.sign(payload, privateKey, { 
  algorithm: 'HS256',
  expiresIn: '1h' 
});
This approach matches Vapi's internal JWT generation and verification exactly as required.
Let me know how it goes.
e
thanks @Shubham Bajaj i've tried jsonwebtoken as well, i believe the same settings without success, but will give it another go 👍
c
Hey Eeloc, a gentle reminder to continue this thread.
2 Views