Can successfully subscribe to WebSocket chat events but receiving no activity

As the title says I am trying to receive events via WebSocket to use in my app, and I am able to authenticate and subscribe to a number of events successfully (code 202), but I don’t seem to be getting any chat events in? At least the events that I can trigger as the broadcaster and that I am interested in (channel.chat.message,channel.chat.clear, and channel.chat.message_delete) are giving me nothing.

I’ve made sure that other events come in (like channel.update, which came in like 20 times but I think I had stale subscriptions lying around for that), so I don’t think it’s a matter of not having access?

FYI I’m working on custom code inside of Godot so all of this is by hand haha

Well this seems wrong since you can’t have stale subscriptions on a websocket.

Short of creating multiple websockets and having then all fire into the same code flow. But then that should be a max of 3 stale subs not 20

You did subscribe to the channel you think you subscribed to?

You can cross check against my rough code example EventSub WebSockets Chat edition with Implicit Auth Example which on auth will subscribed to the authed users chat.

During testing the dozen subscriptions I was making weren’t expiring or being reused, after a few runs I had almost 200 active subscriptions in a “websocket disconnected” state and had to add logic to delete them all before each run.

I’ve sending POST requests to https://api.twitch.tv/helix/eventsub/subscriptions with the usual auth headers and a request body as indicated in the docs, i.e.

{
  "type": "channel.chat.message",
  "version": "1",
  "condition": {
    "broadcaster_user_id": <my id>
  },
  "transport": {
    "method": "websocket",
    "session_id": <my session id>,
  }
}

and so on, and I am getting code 202s for every subscription. My code looks basically the same as your example, at least in that I’m hitting the same endpoints with the same information.

If it helps at all, this is my method for subbing to the events (in GDScript):

var subscriptions := [
	{"type":"channel.update","version":"2"},
	{"type":"channel.follow","version":"2"},
	{"type":"channel.subscribe","version":"1"},
	{"type":"channel.subscription.gift","version":"1"},
	{"type":"channel.subscription.message","version":"1"},
	{"type":"channel.cheer","version":"1"},
	{"type":"channel.raid","version":"1"},
	{"type":"channel.channel_points_custom_reward_redeption.add","version":"1"},
	{"type":"channel.chat.message","version":"1"},
	{"type":"channel.chat.clear","version":"1"},
	{"type":"channel.chat.clear_user_messages","version":"1"},
	{"type":"channel.chat.message_delete","version":"1"},
]

func subscribe_events():
	unsubscribe_all()
	var url = "https://api.twitch.tv/helix/eventsub/subscriptions"
	var headers := [
		"Authorization: Bearer " + global.access_token,
		"Client-Id: " + global.client_id,
		"Content-Type: application/json"
	]
	var data := {
		"condition": {
			"broadcaster_user_id": global.broadcaster_id
		},
		"transport": {
			"method": "websocket",
			"session_id": session_id,
		}
	}
	var merged_data = data.duplicate()
	for sub in subscriptions:
		merged_data.merge(sub)
		var request = HTTPRequest.new()
		add_child(request)
		request.request_completed.connect(
				func(_result:int, response_code:int, _headers:PackedStringArray, _body:PackedByteArray):
					match response_code:
						202:	# Correctly authenticated
							$PanelContainer/RichTextLabel.append_text(
								"[color=green]Successful[/color] subcription to %s.\n" % sub.type
							)
						_:
							$PanelContainer/RichTextLabel.append_text(
								"[color=red]Failed[/color] subscription to %s :: %s\n" % [
									sub.type,
									response_code
								]
							)
					request.queue_free()
		)
		request.request(url, PackedStringArray(headers), HTTPClient.METHOD_POST, JSON.stringify(merged_data))```

For a websocket they’ll self delete after about an hour. (They hang around to help debug why a sub died on it’s own, about an hour for websockets and about 10 days for webhooks)

Otherwise you can just filter Get Subs by state of enabled to ignore stale subs

This is incomplete.

a condition field of user_id is missing…

Whats the body of the response?

I wonder if you have found a weird bug as it should be rejected as your condition is incomplete, as you need to delcare the channel to read the chat of, and which user to read the chat as. I tested it, with the user_id msising from the condition should get a HTTP 400 with

23:24:32Error with eventsub Call channel.chat_settings.update Call: cannot use deprecated subscription type and version
23:24:32Error with eventsub Call channel.chat.clear_user_messages Call: unknown validation error: Key: 'SubscriptionCondition.user_id' Error:Field validation for 'user_id' failed on the 'required' tag
23:24:32Error with eventsub Call channel.chat.notification Call: unknown validation error: Key: 'SubscriptionCondition.user_id' Error:Field validation for 'user_id' failed on the 'required' tag
23:24:32Error with eventsub Call channel.chat.message Call: unknown validation error: Key: 'SubscriptionCondition.user_id' Error:Field validation for 'user_id' failed on the 'required' tag
23:24:32Error with eventsub Call channel.chat.clear Call: unknown validation error: Key: 'SubscriptionCondition.user_id' Error:Field validation for 'user_id' failed on the 'required' tag
23:24:32Error with eventsub Call channel.chat.message_delete Call: unknown validation error: Key: 'SubscriptionCondition.user_id' Error:Field validation for 'user_id' failed on the 'required' tag
{
    "type": "channel.chat.message",
    "version": "1",
    "condition": {
        "broadcaster_user_id": "1337",
        "user_id": "9001"
    },
    "transport": {
        "method": "webhook",
        "callback": "https://example.com/webhooks/callback",
        "secret": "s3cRe7"
    }
}

or from my code

~~snip~~
'channel.chat.message': { version: "1", condition: { broadcaster_user_id, user_id } },
~~snip~~

                fetch(
                    'https://api.twitch.tv/helix/eventsub/subscriptions',
                    {
                        "method": "POST",
                        "headers": {
                            "Client-ID": client_id,
                            "Authorization": `Bearer ${access_token}`,
                            'Content-Type': 'application/json'
                        },
                        "body": JSON.stringify({
                            type,
                            version,
                            condition,
                            transport: {
                                method: "websocket",
                                session_id
                            }
                        })
                    }
                )

which equates to


                fetch(
                    'https://api.twitch.tv/helix/eventsub/subscriptions',
                    {
                        "method": "POST",
                        "headers": {
                            "Client-ID": client_id,
                            "Authorization": `Bearer ${access_token}`,
                            'Content-Type': 'application/json'
                        },
                        "body": JSON.stringify({
                            type,
                            version,
                            condition: {
                                broadcaster_user_id: 'foo',
                                user_id: 'bar'
                            },
                            transport: {
                                method: "websocket",
                                session_id
                            }
                        })
                    }
                )

Basically you condition is correct for your subs except the chat topics which need a user_id as well

Side Note: depending on the use case for the data, you probably want channel.chat.notification rather than the three channel.sub topics and you can get cheering from channel.chat.message once you have repaired the function

Note in my code:

            let topics = {
                'channel.chat.clear': { version: "1", condition: { broadcaster_user_id, user_id } },
                'channel.chat.clear_user_messages': { version: "1", condition: { broadcaster_user_id, user_id } },

                'channel.chat.message_delete': { version: "1", condition: { broadcaster_user_id, user_id } },
                'channel.chat.notification': { version: "1", condition: { broadcaster_user_id, user_id } },
                'channel.chat.message': { version: "1", condition: { broadcaster_user_id, user_id } },

                'channel.chat_settings.update': { version: "beta", condition: { broadcaster_user_id, user_id } }
            }

or from my other example

            let topics = {
                'channel.update': { version: "1", condition: { broadcaster_user_id: user_id } },
                'channel.follow': { version: "2", condition: { broadcaster_user_id: user_id, moderator_user_id: my_id } },
                //inbound raid
                'channel.raid': { version: "1", condition: { to_broadcaster_user_id: user_id } },

                'stream.online': { version: "1", condition: { broadcaster_user_id: user_id } },
                'stream.offline': { version: "1", condition: { broadcaster_user_id: user_id } },

                'user.update': { version: "1", condition: { user_id: user_id } }
            }

Different conditions based on the topic (see channel.follow here)

I think this was the critical question actually! Looking at the returned bodies, I saw I was actually subscribing to channel.update each iteration because my POST data dictionary was made before the loop started, so that merged_data.merge(sub) line is quietly failing to change what subscription I’m asking for…

With that changed, I’m seeing the behavior you anticipated, so I’ll just go ahead and fix up my sub dictionaries to have the correct conditions!