Available today: Twitch Chat on EventSub, an API for sending chat, and the Conduit transport method for EventSub

EventSub delivers real time information for activities that occur on Twitch such as subscriptions, Cheering, Hype Trains, and polls. As extensive as EventSub has become since its initial release, there has been one foundational aspect of Twitch that has not made an appearance until now. Get your favorite emotes ready because sending and receiving Twitch Chat is coming to EventSub and the Twitch API.

An EventSub subscription type for channel chat messages

During TwitchCon 2023, four new subscription types were announced and later released as the first step to bring Twitch Chat to EventSub. These are channel.chat.clear, channel.chat.clear_user_messages, channel.chat.message_delete, and channel.chat.notification. At the same time, we mentioned eventually adding a fifth subscription type for receiving all chat messages in a given channel. After letting it cook a little longer, the channel.chat.message subscription type is now available as version 1.

To subscribe to read chat in a channel, you’ll need authorization from the user your application wants to read chat as (we’ll call this the chatting user). This user must be able to join the specified channel’s chat and will appear in the chatters list as long as your subscription is active. If you’re setting up a subscription over WebSockets, you’ll need the user:read:chat scope from the chatting user. If your app needs to scale out to many channels, you’ll need to subscribe via Conduits or Webhooks, and you must also have the user:bot scope from the chatting user and either (1) the channel:bot scope from the broadcaster or (2) the chatting user must have moderation status from the broadcaster. These additional scopes for app access tokens provides broadcasters with more control over who can programmatically interact with their chat.

Although defined in the documentation, here is a recap of these scopes:

  • channel:bot - Allows the application’s bot users access to a channel. Granted by the broadcaster of the channel you want to read chat in and necessary if you are setting up your subscription with an app access token.
  • user:bot - Allows the application’s to appear as this user in chat. Granted by the user your application will read chat as and necessary if you are setting up your subscription with an app access token. In the future, we may visually distinguish users that appear in chat via this method from other Twitch users.
  • user:read:chat - Allows the application to view live stream chat messages.

A Twitch API endpoint to sent chat messages

Now that developers can receive chat messages via EventSub, they should also be able to send chat messages via the Twitch API. Therefore, the Send Chat Message Twitch API endpoint is now available in open beta. This endpoint will allow you to send a message to a broadcaster’s chat room. As with IRC, your application will be able to send chat messages at a rate limit based on the chatting user.

Similarly to the scopes mentioned above for reading chat, this API endpoint requires the user:write:chat scope at a minimum for app and user access tokens. If an app access token is used, then the endpoint also requires the user:bot scope from the chatting user and either the channel:bot scope from the broadcaster or moderator status in the broadcaster’s channel.

Why add chat to EventSub and the Twitch API?

Building chatbots over an IRC connection has been beneficial for creators since it was introduced; it created the foundation for interactivity in the live streaming community. But there are some inherent issues building applications with the IRC protocol that we wanted to solve with our latest third-party interfaces. For example, it can take an extremely long time to connect to a lot of channels over IRC, which only becomes worse as your application gains popularity. Reconnecting quickly to a lot of channels via IRC can be challenging and expensive. We think this complexity is a barrier to having more chatbot options for broadcasters. If we made it easy to scale your chatbot as it becomes more popular, we could foster a larger chatbot environment that ultimately gives creators more options and features to enhance their communities.

EventSub with Webhooks and WebSockets does not address the reconnect speed issue with IRC. So we created a new transport type called Conduits to help with this issue and give developers easier ways to scale and load balance your application.

Introducing Conduits; an EventSub transport method for scale

We introduced Conduits via RFC 0017 in August 2023 with more details provided during TwitchCon. Conduits are essentially a wrapper transport type that abstracts your EventSub subscriptions from a specific underlying transport (i.e. Webhook or WebSocket) and load balance notifications across shards. A shard is either a Webhook or a WebSocket connection while a Conduit is a collection of shards. Conduits are intended for backend applications and requires an app access token.

This design allows us to solve three particular challenges for developers using EventSub: it allows us to have subscriptions persist across disconnects, shard notifications for applications with many subscriptions, and provide applications a way to receive high-throughput notifications at scale.

To get started or learn more about how to use Conduits, refer to the Handling Conduit Events guide. As you will see from the guide, the key steps to begin implementing this new transport type are:

  1. Create a Conduit and specify the shard count
  2. Connect a type of transport to each shard
  3. Create subscriptions, with an app access token, that reference the conduit ID as the transport

Notifications are sent to shards and an attempt is made to send notifications for a particular channel ID to the same shard for consistency.

EventSub will disable shards if the underlying websocket disconnects, or if the webhook is unhealthy for an extended period of time. If a shard is disabled, EventSub will retry sending the notification on another shard. To quickly address disabled shards, the new conduit.shard.disabled subscription type can be used to listen for status changes.

Conduit API endpoints and subscription type

A series of API endpoints and an EventSub subscription type are also available beginning today to manage Conduits.

What does all of this mean for IRC and the future of Twitch Chat?

There is currently no plan to remove IRC as a third-party supported interface. Legacy systems, from bots to video game integrations, rely on this functionality and any major changes would be overly disruptive. However, as Twitch Chat evolves, our investment in new features will primarily focus on the functionality available via EventSub and the Twitch API.


The new chat message EventSub looks great! However I couldn’t find anything about action messages in there (when a user uses /me), will that be added in later?

Also thank you so much for the fragments array, it’s so much nicer not having to worry about parsing emotes ourselves

1 Like

That a PRIVMSG with ACTION at the start

{"subscription":{"id":"","status":"enabled","type":"channel.chat.message","version":"1","condition":{"broadcaster_user_id":"15185913","user_id":"15185913"},"transport":{"method":"websocket","session_id":""},"created_at":"2024-01-25T22:30:44.196751927Z","cost":0},"event":{"broadcaster_user_id":"15185913","broadcaster_user_login":"barrycarlyon","broadcaster_user_name":"BarryCarlyon","chatter_user_id":"15185913","chatter_user_login":"barrycarlyon","chatter_user_name":"BarryCarlyon","message_id":"24b24337-e1df-457f-9672-be5f619b0028","message":{"text":"\u0001ACTION chat test\u0001","fragments":[{"type":"text","text":"chat test","cheermote":null,"emote":null,"mention":null}]},"color":"#033700","badges":[{"set_id":"broadcaster","id":"1","info":""},{"set_id":"ambassador","id":"1","info":""}],"message_type":"text","cheer":null,"reply":null,"channel_points_custom_reward_id":null}}
1 Like

I see, so then I’d just need to check if event.message.text starts with "ACTION" and also that text of the first element of event.message.fragments does not start with "ACTION"

It’s messy but I’m glad there’s a workaround!

Hi there! I’m looking at this from the perspective of an extension developer. First of all, thanks for offering alternatives to accessing this data.


We develop extensions for card games with two primary features:

  • Enable an interactive hover zones on the stream via an overlay extension
  • Enable chat commands like !deck so that viewers (e.g. on mobile) can get a link to the deck

These features are powered by a piece of software run by the broadcaster that sends game state information to our EBS.

Our chat bots so far have worked like this:

  • whenever our EBS forwards data to PubSub, we cache data in the EBS
  • we have a long-running IRC client that connects to the Twitch IRC servers and regularly queries the EBS for information about online channels and the data it needs to respond to commands.

From a broadcaster & viewer perspective this is “magical” - they simply need to setup the extension, and the chat bot just works.

The new APIs

With these new APIs it appears we need explicit opt-in from the broadcaster channel:bot scope. This would complicate our extension setup process a bit, because right now the entire setup is the broadcaster pasting a token, and we use Twitch JWTs in the background to verify identity - there is no OAuth login required.

The Send Extension Chat Message endpoint and the Chat Capabilities checkbox in the extension setup have existed for a while, but this was never feasible to us because that never gave us read capabilities for chat. So we needed to build the IRC client either way.

My question: is there a way or a plan how we could switch from IRC to EventSub+API in a more seamless manner? We already have streamer opt-in via the extension install, and of course it would be nicer to use the chat identity of our extension rather than maintain a separate bot account (which is what Send Extension Chat message already does). Send Extension Chat Message is also missing features like reply_parent_message_id today, and is only limited to 250 chars (rather than 500).

For example, are there plans for a chat-reading EventSub subscription type that is equivalent to “Send Extension Chat Message” in that it only requires an extension JWT for authentication? If not, I’d be happy to put a UserVoice together.

Extension Chat Messages aren’t actually Chat messages. They’re not sent over IRC at all, which is why 3rd party clients don’t see them, why it uses your Extensions name as if it was the user instead of an actual user account, and why certain chat features aren’t available (such as replying) because it isn’t actually chat. Twitch just renders these messages in a chat window. This is also why if you wish to use the Send Chat Message endpoint you’ll need a user account that you wish to send messages as, because it’s sending actual messages to chat unlike Send Extension Chat Message.

If you require additional permissions from the broadcaster to use your Extension then you will need to send them through the OAuth process, the same as if you needed to request most of the other scopes from the broadcaster. If you only need to read chat client-side then you could connect to chat anonymously within the Extension as that doesn’t require any auth token. If you wish to do it server-side though you may be limited by the upcoming concurrent join limits so you would still need an auth token in that situation.

Extension Chat Messages aren’t actually Chat messages […]

Thanks for the clarification. The distinction doesn’t really matter to us - we simply need some way of responding via Twitch Chat. Whether that happens through chat or something that looks and feels like chat is not important to us.

If you require additional permissions from the broadcaster to use your Extension then you will need to send them through the OAuth process, the same as if you needed to request most of the other scopes from the broadcaster.

Sure, I understand this is how we could do it today. For some of our extensions we also specifically use a Twitch Login as authorization step. I’m mostly writing in this thread to point out that we have a use case where we intentionally chose to not do that, because it significantly reduces the friction of extension setup.

Overall it seems that extensions having chat functionality is something that’s inconsistently supported by Twitch: sending “lookalike” chat messages is supported, but not really helpful if we can’t react to chat commands. So we’d have to treat our chat integration as a completely different application. When I read this thread it seemed like there’s something being done on the front of improving programmatic chat access, so I’m chiming in specifically from the point of an extension developer and our use case.

Either way, our use case is also satisfied by the IRC infrastructure today. Maybe in the long run there’s a future where “chat” extensions can register individual commands and respond to (only) those, a la Discord slash commands.

you may be limited by the upcoming concurrent join limits

I’m not sure which upcoming limits you’re referring to. Do you have a link?

1 Like


Extensions are also applications you don’t need a seperate set of ClientID/keys from Twitch to read/write chat here. You can use your Extensions ClientID and Twitch API Client Secret.

As thats how bits in extensions eventsub operates (and other API calls)

Full details to follow, there is only what was mentioned during Standard Output at Twitch Con and formal write up hasn’t been done yet.

Generally speaking Extensions are more to replace chat spam rather than add to it. Like why use a !spamCommand when a button click can be used instead.

Sure there are usecases for responding to chat. I’m just being general here

As I’m sure you’re well aware, that is not a limitation of IRC, but a limitation imposed by Twitch itself. Claiming this is an inherent limitation of IRC is at best disingenuous.

That really is not less complexity. It adds additional considerations that do not exist with IRC, and where they do, chances are that the IRC support libraries already account for them (except in cases where the issues were caused by Twitch).

To be clear, I’m not saying IRC (or more accurately tmi) is perfect. Far from it. What I am saying is that the justification reads to me like practice for the inevitable sunset on tmi, which I fully expect to happen in the not too distant future.

I.e., it’s going to become even less supported and eventually yanked off life support. tmi has been an afterthought, with only begrudging updates (and no fixes) for a long time. Basic functionality has been deprecated or removed. In some cases, it was replaced with web requests, which made no sense without fully web-based chat (wonder why that was). The direction has been clear for a while.

/me is not part of the IRC standard and never has been. \001ACTION …\001 is an IRC CTCP command. It’s a bit silly that the way to do it on not-IRC is the IRC way.

1 Like

Entirely possible I have no idea what I’m doing, but is this actually live yet? I copied the example command from the reference for creating a conduit.

âžś curl -X POST 'https://api.twitch.tv/helix/eventsub/conduits' \
-H 'Authorization: Bearer myappaccessotken' \
-H 'Client-Id: myclientid' \
-d '{"shard_count": 5}'
{"error":"Bad Request","status":400,"message":"Missing required parameter \"shard_count\""}% 

You didn’t declare an content type header

This should probably be added to the reference

1 Like

That was it. Thanks!

Using EventSub for chat, how do we get the authenticated users available emotes? So far we at least had the possibility to check the available emote sets via GLOBALUSERSTATE, even if the Helix endpoints are broken and don’t work for some emote sets, as well as the emote set list being incomplete in the first place, but if the future is EventSub, something like this is missing entirely. Without that, we have no way of getting information about e.g. what emotes a bot can use in its messages or a chat client should show in a menu.

There is a UserVoice requesting an endpoint to get that information somehow, but we haven’t gotten an update on that since 2021. This is the only major TMI feature I could find that has no migration path to EventSub and it would be great to get clarification on what the plan is there.

Soon :tm:

One more thing, is there a way of observing if the authenticated account is banned or timed out? I can’t see anything that would give information on bans/TOs that isn’t behind a moderator scope, at which point you couldn’t get timed out on that account anyways.

1 Like

The user’s topics are revoked to the channel they are subscribed to

Essentially a to’ed/banned user gets “disconnected” from the channel by having the topics revoked.

And if you try to reconnect/resub then you get a auth fail.

Okay yeah so I just did some testing myself, but what I really don’t get is why the topic gets revoked even on a TO. Makes sense if you are banned from that chat, but e.g. a 1s timeout to clear all messages really shouldn’t revoke the topic. The timeout in general doesn’t prevent you from reading chat, even while being timed out, you can still subscribe to the topic just fine (makes sense so far), so why disconnect the user for a timeout? And since there is no indication of whether the revocation was a permanent ban or a timeout, the only thing we can do is try to resubscribe and see if it works…

1 Like

I have no further information it’s just how it works

It depends entirely on what you define as complexity. My bot currently scales up to over 3k stateful IRC connections, and outages/upgrades cause downtime to channels unless you roll your own handoff system. Conduits take the complexity out of scaling stateful IRC connections, meaning for most developers it’s now a breeze to join millions of channels.

So yes, it’s probably a bit more complex at the small scale to use Conduits. But at a large scale, it’s much less complex.


I’ll skip the bit where you talk about the complexity of managing a bot as widely used as yours, because that’s inevitable, regardless of what Twitch does.

So it seems to me, you are basically talking about the complexity of working around limitations Twitch imposes, or am I missing something?