[Solved] Token Generated by Extension Helper Shows "Error (403): JWT could not be verified"


I followed the reference and use this code to console.log token at front end.

window.Twitch.ext.onAuthorized(function(auth) {
  console.log('The JWT that will be passed to the EBS is', auth.token);
  console.log('The channel ID is', auth.channelId);

Then I copy the token and tried with curl to send to Pubsub:

curl -H "Authorization: Bearer my-token" \
-H "Client-Id: my-client-id" \
-H "Content-Type: application/json" \
-d '{"content_type":"application/json", "message":"{\"foo\":\"bar\"}", "targets":["global"]}' \
-X POST https://api.twitch.tv/extensions/message/all

But got this error
{"error":"Forbidden","status":403,"message":"Error (403): JWT could not be verified"}

I also tried in my backend node express app:

const secret = Buffer.from('my-secret', 'base64')
router.post('/', async (req, res) => {
  jwt.verify(req.body.token, secret, function (err, decoded) {

It shows message: 'invalid signature'

But I tried the token in online jwt verifier and works.

Did I understand the doc in the wrong way?


You can’t use a token from onAuthorized to send a global message. Viewers have no permissions to send messages on pubsub at all, and broadcasters can only broadcast to their own channel.

The only way to send messages globally is through your EBS creating the required token.

As for why you can’t verify the token signature on your backend, I can only assume you’re either not using bodyparser (have you tried to log req.body.token to ensure you’re actually getting what you think you’re getting?) or you’ve erroneously copied either the JWT when making the request, or the secret used.

As for jwt.io, did you paste the JWT or the secret first? If you paste the token first, then whatever secret your type/paste will automatically update the token to use that as a signature (so works for signing a token that you intend to send). If you want to use it to verify a token you’ve received you need to first paste the secret and then the JWT to ensure it is verified correctly.


Thanks! I referred the code of the extension tutorial AskACaster

router.post('/question', async (req, res) => {
  let {channelId, clientId} = req.body.auth;
  let token = app.getToken(req);
  let put = await postQuestion(req.body);
  let twitchpubsubPost = await postToTwitchPubSub('newquestion', token, channelId, clientId);


The token they used here is from auth.token.

But anyway I used the jwt I generated in backend using the code below:

const secret = Buffer.from('my-extension-secret', 'base64')

router.post('/pubsub', async (req, res) => {
  try {
    let { channel_id, client_id } = await req.body;
    let serverToken = makeServerToken(channel_id)
    let requestPubSub = await reqPubSub('newquestion', serverToken, channel_id, client_id);
    console.log("requestPubSub: ", requestPubSub)
  catch (e) {
const reqPubSub = async (message, serverToken, channelId, clientId) => {
    url: `https://api.twitch.tv/extensions/message/${channelId}`,
    headers: {
      Accept: 'application/vnd.twitchtv.v5+json',
      Authorization: 'Bearer ' + serverToken,
      'Client-ID': clientId,
      'Content-Type': 'application/json'
    body: JSON.stringify({
      message: message,
      content_type: 'application/json',
      targets: ['broadcast']
    gzip: true
  }, function (e, r, b) {
    if (e) {
    } else if (r.statusCode == 204) {
      console.log('Ok to ' + channelId);
    } else {
      console.log('Got ' + r.statusCode + ' to ' + channelId);

function makeServerToken(channelId) {
  const payload = {
    exp: Math.floor(new Date().getTime() / 1000) + 600,
    channel_id: String(channelId),
    role: 'external',
    pubsub_perms: {
      send: ['broadcast']
  return jwt.sign(payload, secret);

I used PostMan to test, backend logged Ok to xxx. But nothing shows up at frontend. I have a listen in componentDidMount():

if (this.twitch) {
  this.twitch.listen('broadcast', (target, contentType, body) => {
  this.twitch.rig.log(`New PubSub message!\n${target}\n${contentType}\n${body}`)

I have this error when I refresh my page:

Uncaught (in promise) ERR_BADAUTH
value @ twitch-ext.min.js:22
value @ twitch-ext.min.js:1
value @ twitch-ext.min.js:22

This error comes out only when the page is refreshed not when I send to PubSub.
But it says bad auth so I assume the token from EBS does not work?

Is there anything wrong in my code when generating token?


Okay I figured it out. I got that error because generated a new token on Extension Dashboard. I need to close the project in Rig and re-open it by typing in the new token.
Now it works nicely and I can get Pubsub messages!

I also tried to post auth.token from front end instead of copy-paste and send from PostMan. It also works. Perhaps something happened when I copy-paste.