Invalid transport and auth combination error

Please lmk if this isn’t a question for here (I’m really new to this).

I’m trying to subscribe to get notified whenever certain streamers go live, but for some reason I keep getting the error { error: 'Bad Request', status: 400, message: 'invalid transport and auth combination' }

I’m not sure what I’m doing wrong and would really appreciate any help. I put my whole code down below, as well as the docs pages I was looking at.

import axios from 'axios';
import WebSocket from 'ws';

const clientId = '7fv78wh6keb4j3c8oeoff979ejifdh';
const clientSecret = 'SECRET';
const channelName = 'CHANNEL_HERE';


export default class wsmanager {
    /** @type { WebSocket } */
    #ws;

    #heartbeatint;

    #authheaders;

    #startHeartbeat() {
        const timeBetweenPings = 30000;
        this.#heartbeatint = setInterval(() => this.#ws.send(), timeBetweenPings);
        console.log(`heartbeat started with interval ${timeBetweenPings}ms`);
    }


    async getTwitchOAuthToken() {
        const tokenUrl = 'https://id.twitch.tv/oauth2/token';
        const params = new URLSearchParams({
            client_id: clientId,
            client_secret: clientSecret,
            grant_type: 'client_credentials',
        });

        try {
            const response = await axios.post(tokenUrl, params);
            const { access_token } = response.data;
            return access_token;
        } catch (error) {
            console.error('Error obtaining OAuth token:', error.response.data);
            throw error;
        }
    }


    async getBroadcasterUserId(username, token) {
        const userUrl = `https://api.twitch.tv/helix/users?login=${username}`;

        try {
            const response = await fetch(userUrl, {
                method: 'GET',
                headers: this.#authheaders,
            });

            if (!response.ok) {
                throw new Error(`Error fetching user data: ${response.statusText}`);
            }

            const data = await response.json();
            if (data.data.length === 0) {
                throw new Error(`User not found: ${username}`);
            }

            const userId = data.data[0].id;
            return userId;
        } catch (error) {
            console.error('Error fetching broadcaster user ID:', error);
            throw error;
        }
    }


    async connectToWebSocket(token) {
        this.#authheaders = {
            'Authorization': `Bearer ${token}`,
            'Client-Id': '7fv78wh6keb4j3c8oeoff979ejifdh',
            'Content-Type': 'application/json'
        };

        const wsUrl = 'wss://eventsub.wss.twitch.tv/ws',
            uid = await this.getBroadcasterUserId(channelName, token)

        this.#ws = new WebSocket(wsUrl);

        this.#ws.on('open', () => {
            console.log('Connected to Twitch WebSocket');
            // Optionally, you can authenticate or subscribe to events here
        });

        this.#ws.on('message', (data) => {
            const message = JSON.parse(data);

            if (message.metadata && message.metadata.message_type === 'session_welcome') {
                this.#startHeartbeat();
                this.subscribeToStreamOnline(uid, token, message.payload.session.id);
            }
        });

        this.#ws.on('close', (ev) => {
            console.log('WebSocket connection closed with code', ev);
        });

        this.#ws.on('error', (error) => {
            console.error('WebSocket error:', error);
        });
    }

    async subscribeToStreamOnline(userId, token, session_id) {
        const eventSubUrl = 'https://api.twitch.tv/helix/eventsub/subscriptions';

        const body = {
            type: 'stream.online',
            version: '1',
            condition: {
                'broadcaster_user_id': userId,
            },
            transport: {
                'method': 'websocket',
                'session_id': session_id
            },
        };

        try {
            const response = await fetch(eventSubUrl, {
                method: 'POST',
                headers: {
                    'Authorization': `Bearer ${token}`,
                    'Client-Id': clientId,
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(body),
            });

            if (!response.ok) {
                console.error(await response.json());
                throw new Error();
            }

            const data = await response.json();
            console.log('Subscription successful:', data);
        } catch (error) {
            console.error('Error subscribing to event:', error);
            throw error;
        }
    }

    async init() {
        try {
            const token = await this.getTwitchOAuthToken();
            await this.connectToWebSocket(token);
        } catch (error) {
            console.error('Failed to connect or subscribe:', error.message);
        }
    }
    constructor() { };
}


const temp = new wsmanager();
temp.init();

Straight websockets requires a user access token. You have used an app access

Hence the error

ohhhh I see, thank you! Does the user access token have to be for the user who’s websocket I’m connecting to? I ask because I thought the connection was one-way so the app couldn’t change anything (please correct me if I’m wrong)

For public topics no, and you mean the “broadcaster ID on the topic conditions doesn’t need to match the user ID in the oAuth token”

Websockets is just a transport.

Not sure what you are asking here.

Striaght websockets are basically for front end applications where the user using the app logs in via implict auth or DCF, both only require a ClientID to go.

Sure you can use websockets on the server, but the same way a chatbot works with the user token of the bot.

Or under conduits but I dont think this fits your described use case.

Sorry I phrased the question strangely, I accidentally sent something to the websocket connection at some point (that wasn’t a PONG) and it return an unauthorized status, so I assumed it wouldn’t need the user access token. In terms of getting the token, I was wondering what scopes I would need to get a websocket event if the user is live (I assume it wouldn’t just be read:user but I might be wrong)

If you send anything to a websocket you get disconnected with a 4001 code and message Client sent inbound traffic

stream.online requires no scopes it is a public topic.

If you connect a websocet and the userID in the condition is the same as the userID in the token then the topic is cost 0. Otherwise cost 1.

1 Like