Trying to subscribe to an EventSub using WebSockets gives an 403 error message

Hi, I am creating a game where you can connect your Twitch account to an application I created. In order to read the chat I am trying to subscribe to an EventSub using a WebSocket but something do not allow me to do this.

  1. I authorize the app with some scopes (channel:bot, user:bot, user:read:chat, user:write:chat)
  2. I get a user access token using my Twitch channel
  3. I validate the token (this step will be the first if there is already a saved token which is not expired)
  4. I get the id of the broadcaster (my channel) and the id of the app
  5. I open the websocket to the url wss://eventsub.wss.twitch.tv/ws
  6. I receive the welcome message
  7. I try to subscribe the EventSub with the url https://api.twitch.tv/helix/eventsub/subscriptions

This last step is the one that do not let me connect. Here is the piece of code:

async Task<bool> EventSubSubscription(string token, string clientId, string sessionId, string broadcasterUserId, string userId) {
    string body;
    ApiRespose_EventSub apiResponseData;
    ApiRequest_EventSub apiRequestData;

    HttpRequestMessage httpRequest;
    string returnData;

    httpClient.BaseAddress = null;
    httpClient.DefaultRequestHeaders.Clear();

    httpRequest = new HttpRequestMessage(HttpMethod.Post, "https://api.twitch.tv/helix/eventsub/subscriptions");
    httpRequest.Headers.TryAddWithoutValidation("Authorization", "Bearer " + token);
    httpRequest.Headers.TryAddWithoutValidation("Client-Id", clientId);
    httpRequest.Headers.TryAddWithoutValidation("Content-Type", "application/json");

    apiRequestData = new ApiRequest_EventSub
    {
        type = "channel.chat.message",
        version = "1",
        condition = new ApiRequest_EventSub_Condition
        {
            broadcaster_user_id = broadcasterUserId,
            user_id = userId,
        },
        transport = new ApiRequest_EventSub_Transport
        {
            method = "websocket",
            session_id = sessionId,
        },
    };

    body = JsonUtility.ToJson(apiRequestData);

    httpRequest.Content = new StringContent(body, Encoding.UTF8, "application/json");

    // send request and wait for it to complete
    Task<HttpResponseMessage> httpRespose = httpClient.SendAsync(httpRequest);
    while (!httpRespose.IsCompleted) {
        await Task.Delay(10);
    }

    // fetch response content
    Task<string> httpResponseContent = httpRespose.Result.Content.ReadAsStringAsync();
    while (!httpResponseContent.IsCompleted) {
        await Task.Delay(10);
    }

    // return the response content and be done - calling an API can be so easy :-D
    returnData = httpResponseContent.Result;

    // parse the return JSON into a more usable data object
    apiResponseData = JsonUtility.FromJson<ApiRespose_EventSub>(returnData);
    Debug.Log(returnData);

    try {
        return apiResponseData.data[0].condition.broadcaster_user_id == broadcasterUserId;
    } catch (Exception e) {
        Debug.LogError(e);
        return false;
    }
}

The errore message that appears is {“error”:“Forbidden”,“status”:403,“message”:“subscription missing proper authorization”}

I do not understand what I am doing wrongly, so I ask you for help.
Thank you in advance.

What do you mean by ID of the App?

broadcaster_user_id is the channel ID you want to connect to 26610234 not cohhcarnage
user_id is the user ID of the token so 15185913 not barrycarlyon and the user that the token is for that you did in step 1

For straight websockets you only need user:read:chat to read (write for sending message via the API)

You can cross reference what you are doing with this example: EventSub WebSockets Chat edition with Implicit Auth Example

BarryCarlyon
What do you mean by ID of the App?

I am using both IDs accordingly to what I’ve read here Sending and Receiving Chat Messages | Twitch Developers
I do not know if it is correct but I am using my channel ID for the broadcaster_user_id (the ID, not the login name) and the ID of my app user for the user_id (the ID of the account I created in order to make the application I want to use in my game)

BarryCarlyon
broadcaster_user_id is the channel ID you want to connect to 26610234 not cohhcarnage
user_id is the user ID of the token so 15185913 not barrycarlyon and the user that the token is for that you did in step 1

Perhaps there is something I do not understand but the channel ID and the user ID of the token are the same for me. The account I used for creating the application is different from the one I use to connect to it, and both IDs I get belong to the latter (and obviously they are exactly the same). Am I wrong?

I have just used the same ID (the channel ID) for both broadcaster_user_id and user_id and now it works. Shame on me because I have not tried it before!

However, I do not understand why it works. In every part of the API I cannot find this solution and I was thinking that it could not have been like this. If you are up to it, please, can you explain why does it work?

Anyway, thank you for your answer.

That sounds correct.

But thats not an “app id”, and app id is more the client_id than anything. Applications don’t have a user attached to them.

And the owner of the clientID is irrelevant.

So your call looks like?

As it should of worked, as long as the user_id is the ID of the user you have authenticated as.
And the target channel doesn’t have the user you are reading chat as banned.

But this is irrelevant to your use case (as a game running client side).

You authed in as bob you should be able to connect to whichever channel you want, (as long as bob is not banned in that channel, and you are below the concurrent join limit)

To connect to your own channel to read chat, that would be correct

This type channel.chat.message works as follows

conditions:

  • broadcaster_user_id - the chat you want to read
  • user_id - the user you want to read chat as and thus oAuth’ed as.

(For Straight websockets)

Becuase you authenticated as bob and said I want to read chat as bob and then said I want to read the chat channel of bob

And you only have the permission of bob

bob here being the user you oAuth-enticated as (not but could be the owner of the application)

So for example for conduits

I barrycarlyon of ID 15185913 own b2z6u6b4sg6p84d3pq1ykcehlk1vhh9
I get cohhcarnage of ID 26610234 to authenticate to that clientID
And I get the channelbot CohhilitionBot of ID 45522171 to authentcate to that ClientID

Now the bot 45522171 can connect to 26610234 channel via conduits, (as in this example I’m using condiuts)

And my ID (the owner of the application) comes up no where.

Thats why I linked my reference example I wrote so you can compare

So here when using websockets you’ll only be able to authenticate as the broadcaster playing the game.

So I would, in my game,

  • present an oAuth prompt
  • authenticate the broadcaster with chat read only scopes not send
  • connect to and read chat as the broadcaster, so broadcaster_user_id and user_id are the same, as I’m connecting to the broadcasters chat as the broadcasters
  • send any prompts or nudges for chat to do things in the game via video not via chat, so I don’t have to request write scopes.

The person running the game will only have the permissions of themself available and no other users.

Thank you very much for the explanation, now I understand better how all of this works.

BarryCarlyon
send any prompts or nudges for chat to do things in the game via video not via chat, so I don’t have to request write scopes.

And yes, this is exactly what I am trying to do.

You’ve been very helpful. Best regards.