Questions about PubSub's Wisper

I would like to use PubSub’s Wisper to send messages only to specified users.
In the process of implementing this, I don’t seem to be able to communicate properly with the front-end side.
Is there a sample somewhere?

this reply was written BEFORE coffee so I totally answered the wrong question with an answer

Only the broadcasters JWT will be able to do this. ← Ignore I can’t read before coffee

Note: it doesn’t send a Whisper whisper, it just sends a Extension PubSub message to the extension front end that the user is using (and it eats the channel rate since it’s send to channel+user combo)

This should work, again, only with the Broadcasters JWT in a “front end only” scenario.
Generally I only/always do all my Extension PubSub traffic via the EBS. And sign an external JWT.

(Not tested just written off the top of my head)

            method: 'POST',
            headers: {
                'Client-ID': the_extension_client_id,
                'Authorization': 'Bearer ' + window.Twitch.ext.viewer.sessionToken
            body: JSON.stringify({
                target: ['whisper-USERSOPAQUEID'],
                is_global_broadcast: false,
                message: JSON.stringify(YOUR MESSAGE PAYLOAD)
    .then(resp => {
    .catch(err => {

Note: you will need the target users OPAQUEID in order to broadcast to them.

You might find it easier to send the messages over the channel or global feeds and include the “true” user_id in the messages so your frontend knows to parse it for that user.

I am writing this, but it does not work correctly.
Am I doing something wrong?


$(function () {
window.Twitch.ext.listen(‘whisper-USERSOPAQUEID’ , function (topic, contentType, message) {
console.log('Received message: ’ + message);

Body parameters in EBS

var parameters = new Dictionary<string, Object>(){
{ “content_type”, “application/json” },
{ “message”, “pubSub!” },
{ “broadcaster_id”, channelId},
{ “is_global_broadcast”, false},
{ “target”, new List(){ “whisper-USERSOPAQUEID” } }


“exp”: 123456,
“channel_id”: “123456789”,
“user_id”: “123456789”,
“role”: “external”,
“pubsub_perms”: {
“send”: [

exp, channel_id, and user_id are replaced.

This is what I get for doing this pre-coffee I misread your post and thought this was pure front end

Should be

window.Twitch.ext.listen(‘whisper-’ + window.Twitch.ext.viewer.opaqueId, function (topic, contentType, message) {
console.log('Received message: ’ + message);

And this nodeJS example will do the trick:

`use strict`;

const fs = require('fs');
const path = require('path');

let config = JSON.parse(fs.readFileSync(path.join(

let config = {
    "client_id": "",
    "client_secret": "",
    "extension_secret": "",
    "owner_id": "",
    "version": ""

let broadcaster_id = '';
let target_user_id = '';

const jwt = require('jsonwebtoken');
const got = require('got');

const secret = Buffer.from(config.extension_secret, 'base64');

let sigPayload = {
    'exp':          Math.floor(new Date().getTime() / 1000) + 60,
    'user_id':      config.owner_id,
    'role':         'external',
    'channel_id':   broadcaster_id,
    'pubsub_perms': {
        'send': [
            'whisper-' + target_user_id
let sig = jwt.sign(sigPayload, secret);

    url: '',
    method: 'POST',
    headers: {
        'Client-ID': config.client_id,
        'Authorization': 'Bearer ' + sig,
        'Content-Type': 'application/json'
    body: JSON.stringify({
        target: ['whisper-' + target_user_id],
        is_global_broadcast: false,
        message: JSON.stringify({
            stuff: 'thing'
    responseType: 'json'
.then(resp => {
    console.log(resp.statusCode, resp.body);
.catch(err => {
    if (err.response) {
        console.error('API ERROR', err.response.statusCode, err.response.body);
    console.error('BAD ERROR', err);

{ “content_type”, “application/json” },

Is no longer needed in the body params thats an OLD PubSub API thing

What http code and body message are you getting back?

What http code and body message are you getting back?

StatusCode: 204 will be returned.

It’s almost the same description, but I don’t know what the problem is.

For additional information, I am using TwitchDeveloperRig.

Will be returned? Or thats what you are getting?

Sounds like you are not using the correct opaqueID

You need to obtain the window.Twitch.ext.viewer.opaqueId from the frontend of the user that you wish to message.

And opaqueID (Required Technical Background | Twitch Developers) is not the users real Twitch ID, but their “extension user session ID” so it starts with a ‘A’ or a ‘U’ (Extensions | Twitch Developers)

So 204, means everything worked on Twitch’s side, which suggests to me that the opaque ID you are sending to is not correct.

So when you listen here

window.Twitch.ext.listen(‘whisper-’ + window.Twitch.ext.viewer.opaqueId

You need to use the same ID when sending.

This shouldn’t make a difference.

But make sure the extension is installed and active on the channel you are using in the view (just to be on the same side)

And make sure the view has the right channelID applied to it.
You can get the opaque ID from the view itself.

Will be returned? Or thats what you are getting?

This is the actual code I got.

In the frontend, I believe it is userId=opaqueID=window.Twitch.ext.viewer.opaqueId that you can get when onAuthorized.
I have tried to use this ID to listen but did not get any notification.

window.Twitch.ext.listen(‘whisper-’ + window.Twitch.ext.viewer.opaqueId

ChannelID is also obtained from the communication from the frontend to the EBS and is used for private use, so I am sure it is correct.

Isn’t this the same issue with Rig?

Having taken a moment to test this in the rig this morning.
And looking at the bug you found.

Yes the whisper topic doesn’t seem to work in the developer rig.

This is likely due to the rig “pretending” to be a user, so the “user” specific topic doesn’t work for security reasons. I do 99.9% of my extension development on Twitch rather than the Rig itself.

Depending on your expected send count, you might want to be using the main channel feed.

As then you can send an array of messages in one go.
And your frontend can extract the message for that user from the array
Which then is a more efficent usage of the per channel rate limit, since the whisper topic uses the channel rate limit.

Also saves having to juggle whispertarget ID’s

I see.
I’ll look into developing without Rig.
By the way, may I have the URL for the main channel feed?

What do you mena URL to the main channel feed?

I would like to know the URL of the explanation page.

The explanation page to what?

I have no idea what you want