# How to send message with eventsub

**URL:** <https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483>\
**Category:** API\
**Created:** [December 14, 2025, 8:22pm UTC](https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483 "2025-12-14T20:22:34Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![unlucky](https://avatars.discourse-cdn.com/v4/letter/u/f07891/32.png) [@unlucky](https://discuss.dev.twitch.com/u/unlucky)\
**Post date:** [December 14, 2025, 8:22pm UTC](https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483/1 "2025-12-14T20:22:35Z")

</div>

i have with code

```auto
from twitchAPI.twitch import Twitch
from twitchAPI.chat import Chat
from twitchAPI.helper import first
from twitchAPI.eventsub.webhook import EventSubWebhook
from twitchAPI.object.eventsub import ChannelFollowEvent, ChannelChatMessageEvent
from twitchAPI.oauth import UserAuthenticator
from twitchAPI.type import AuthScope
from loguru import logger
import asyncio

EVENTSUB_URL = '#'
APP_ID = '#'
APP_SECRET = '#'
TARGET_SCOPES = [AuthScope.CHANNEL_BOT, AuthScope.USER_WRITE_CHAT]
mod_id = "#"
br_id = "#"

async def on_follow(data: ChannelFollowEvent):
    print(f'{data.event.user_name} now follows {data.event.broadcaster_user_name}!')

async def on_message(data: ChannelChatMessageEvent):
    print("get message")

async def eventsub_webhook_example():
    logger.info("start twitch")
    try:
        twitch = await Twitch(APP_ID, APP_SECRET)
        logger.info("success twitch")
    except Exception as e:
        logger.error(e)

    logger.info("start auth")
    try:
        token = "#"
        refresh_token = "#"
        await twitch.set_user_authentication(token, TARGET_SCOPES, refresh_token)
        logger.info("end auth")
    except Exception as e:
        logger.error(e)

    eventsub = EventSubWebhook(EVENTSUB_URL, 80, twitch)
    await eventsub.unsubscribe_all()
    eventsub.start()
    await eventsub.listen_channel_follow_v2(br_id, mod_id, on_follow)
    await eventsub.listen_channel_chat_message(br_id, mod_id, on_message)
    await twitch.send_chat_message(br_id, mod_id, "bot start")

    try:
        input('press Enter to shut down...')
    finally:
        await eventsub.stop()
        await twitch.close()
    print('done')

asyncio.run(eventsub_webhook_example())

```

in chatters list bot has badge “chat bot“  
but when i send with “await twitch.send\_chat\_message(br\_id, mod\_id, “bot start”)“ i get message without badge

what i can do?

---

<div class="post-metadata">

**Author:** ![Dist](https://d3ovvz0v2ajtpu.cloudfront.net/user_avatar/discuss.dev.twitch.com/dist/32/7192_2.png) [@Dist](https://discuss.dev.twitch.com/u/Dist)\
**Post date:** [December 14, 2025, 9:40pm UTC](https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483/2 "2025-12-14T21:40:21Z")

</div>

EventSub is unrelated to sending chat messages it’s just for listening.

To send a chat message you need to use the [Send Chat Message](https://dev.twitch.tv/docs/api/reference/#send-chat-message) endpoint, with an App access token (so this can only be done server-side, with a confidential app). You also need to have the chatting user granting the `user:bot` scope to your app, and either that user be a moderator in the channel you’re sending messages to or the channel granting your app the `channel:bot` scope.

Because you’re not getting the badge when you’re sending messages, you’re likely using a User Token, rather than App token.

---

<div class="post-metadata">

**Author:** ![unlucky](https://avatars.discourse-cdn.com/v4/letter/u/f07891/32.png) [@unlucky](https://discuss.dev.twitch.com/u/unlucky)\
**Post date:** [December 15, 2025, 12:28pm UTC](https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483/3 "2025-12-15T12:28:12Z")

</div>

how to get app access token with the needed scopes?  
i do with

```auto
from twitchAPI.twitch import Twitch
from twitchAPI.chat import Chat
from twitchAPI.helper import first
from twitchAPI.eventsub.webhook import EventSubWebhook
from twitchAPI.object.eventsub import ChannelFollowEvent, ChannelChatMessageEvent
from twitchAPI.oauth import UserAuthenticator
from twitchAPI.type import AuthScope
from loguru import logger
import asyncio
import requests
import json

def to_serializable(obj):
    if obj is None:
        return None
    if isinstance(obj, (str, int, float, bool)):
        return obj
    if hasattr(obj, ' __dict__'):
        return {k: to_serializable(v) for k, v in obj. __dict__.items()}
    if hasattr(obj, 'dict'):
        return to_serializable(obj.dict())
    return str(obj)

url = 'https://api.twitch.tv/helix/chat/messages'

app_token = None

headers = {
    'Authorization': f'Bearer {app_token}',
    'Client-Id': '#',
    'Content-Type': 'application/json'
}

EVENTSUB_URL = '#'
APP_ID = '#'
APP_SECRET = '#'
TARGET_SCOPES = [AuthScope.CHANNEL_BOT, AuthScope.USER_WRITE_CHAT, AuthScope.USER_BOT]
scopes = [AuthScope.USER_WRITE_CHAT, AuthScope.USER_BOT]
mod_id = "#"
br_id = "#"

async def on_follow(data: ChannelFollowEvent):
    print(f'{data.event.user_name} now follows {data.event.broadcaster_user_name}!')

async def on_message(data: ChannelChatMessageEvent):
    send_data = {
        "broadcaster_id": "#",
        "sender_id": "#",
        "message": f"Echo: {data.event.message.text}",
        "for_source_only": True
    }

    response = requests.post(url, headers=headers, json=send_data)
    print(response.status_code)
    print(response.json())
    print("get message")

async def eventsub_webhook_example():
    logger.info("start twitch")
    twitch = await Twitch(APP_ID, APP_SECRET)
    logger.info("success twitch")

    logger.info("start auth")
    token = "#"
    refresh_token = "#"
    await twitch.set_user_authentication(token, TARGET_SCOPES, refresh_token)
    logger.info("end auth")

    global headers
    app_token = twitch.get_app_token()
    await twitch.set_app_authentication(app_token, scopes)
    headers['Authorization'] = f'Bearer {app_token}'
    logger.info(f"app token is getting")

    eventsub = EventSubWebhook(EVENTSUB_URL, 80, twitch)
    await eventsub.unsubscribe_all()
    eventsub.start()
    await eventsub.listen_channel_follow_v2(br_id, mod_id, on_follow)
    await eventsub.listen_channel_chat_message(br_id, mod_id, on_message)

    try:
        logger.success("BOT STARTED. Ctrl+C for stop...")
    except KeyboardInterrupt:
        logger.info("\nStop...")
    finally:
        await eventsub.stop()
        await asyncio.sleep(1)
        await twitch.close()
    print('done')

if __name__ == " __main__":
    asyncio.run(eventsub_webhook_example())

```

but with when I try to send a message  
{‘error’: ‘Unauthorized’, ‘status’: 401, ‘message’: ‘The sender must have authorized the app with the user:write:chat and user:bot scopes.’}

---

<div class="post-metadata">

**Author:** ![BarryCarlyon](https://d3ovvz0v2ajtpu.cloudfront.net/user_avatar/discuss.dev.twitch.com/barrycarlyon/32/253_2.png) [@BarryCarlyon](https://discuss.dev.twitch.com/u/BarryCarlyon)\
**Post date:** [December 15, 2025, 12:39pm UTC](https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483/4 "2025-12-15T12:39:58Z")

</div>

> [@unlucky](#):
>
> how to get app access token with the needed scopes?

App Access tokens don’t have scopes

The steps are:

- First you get an oAuth token from the broadcaster with `channel:bot`
- Then you get an oAuth token from the account that is the bot with `user:bot` `and the relevant chat scopes: user:write:chat`
- Store both user tokens if needed.

THEN you generate an app access token and use that with Send Chat. (note the scopes listed as minimal and you may need more for other operations) ([My Reference Documenation](https://github.com/BarryCarlyon/twitch_misc/tree/main/eventsub/websockets/web/chat#how-does-authentication-work) this is for eventsub but the logic applies on how authentication operations, just for send chat needs different/extra scope(s))

Conceptually you already did some of these steps to get eventsub working (with the chat bot tag in the user list) as that requires `channel:bot`/`user:bot` from the various users.

The Send Chat API uses “prior user authentication” to allow access to send (in the same way as doing prior auth allows eventsub conduits or webhooks to allow type/topic access as that uses an app access token also)

---

<div class="post-metadata">

**Author:** ![unlucky](https://avatars.discourse-cdn.com/v4/letter/u/f07891/32.png) [@unlucky](https://discuss.dev.twitch.com/u/unlucky)\
**Post date:** [December 15, 2025, 7:53pm UTC](https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483/5 "2025-12-15T19:53:10Z")

</div>

it worked thanks!

new question:  
app token have “expires\_in“  
how to refresh it or i need to create a new one every time?

---

<div class="post-metadata">

**Author:** ![unlucky](https://avatars.discourse-cdn.com/v4/letter/u/f07891/32.png) [@unlucky](https://discuss.dev.twitch.com/u/unlucky)\
**Post date:** [December 15, 2025, 8:27pm UTC](https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483/6 "2025-12-15T20:27:51Z")

</div>

and as I understand it, I can’t combine Eventsub and IRC. Right?

---

<div class="post-metadata">

**Author:** ![Dist](https://d3ovvz0v2ajtpu.cloudfront.net/user_avatar/discuss.dev.twitch.com/dist/32/7192_2.png) [@Dist](https://discuss.dev.twitch.com/u/Dist)\
**Post date:** [December 15, 2025, 8:33pm UTC](https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483/7 "2025-12-15T20:33:18Z")

</div>

For app tokens, you just generate a new one when current one expires.

As for combining EventSub and IRC, there’s nothing stopping you connecting to and using both services, how you ‘combine’ them will be entirely up to you within your app.

---

<div class="post-metadata">

**Author:** ![unlucky](https://avatars.discourse-cdn.com/v4/letter/u/f07891/32.png) [@unlucky](https://discuss.dev.twitch.com/u/unlucky)\
**Post date:** [December 16, 2025, 1:01pm UTC](https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483/8 "2025-12-16T13:01:13Z")

</div>

I tried listening to two channels and everything worked.

```python
    await eventsub.listen_channel_chat_message(br_id, mod_id, on_message)
    await eventsub.listen_channel_chat_message(shem_id, mod_id, on_message)

```

Question: Can I use this setup to listen to all the channels I want, or is there a limit to the number of channels I can listen to?

---

<div class="post-metadata">

**Author:** ![Dist](https://d3ovvz0v2ajtpu.cloudfront.net/user_avatar/discuss.dev.twitch.com/dist/32/7192_2.png) [@Dist](https://discuss.dev.twitch.com/u/Dist)\
**Post date:** [December 16, 2025, 1:28pm UTC](https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483/9 "2025-12-16T13:28:26Z")

</div>

You can find the limits regarding EventSub over websockets here: [https://dev.twitch.tv/docs/eventsub/handling-websocket-events/#subscription-limits](https://dev.twitch.tv/docs/eventsub/handling-websocket-events/#subscription-limits), or if using Conduits: [https://dev.twitch.tv/docs/eventsub/handling-conduit-events/#limits](https://dev.twitch.tv/docs/eventsub/handling-conduit-events/#limits)

---

<div class="post-metadata">

**Author:** ![BarryCarlyon](https://d3ovvz0v2ajtpu.cloudfront.net/user_avatar/discuss.dev.twitch.com/barrycarlyon/32/253_2.png) [@BarryCarlyon](https://discuss.dev.twitch.com/u/BarryCarlyon)\
**Post date:** [December 16, 2025, 1:36pm UTC](https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483/10 "2025-12-16T13:36:01Z")

</div>

> [@unlucky](#):
>
> is there a limit to the number of channels I can listen to?

If you have authentication from the user, then you have permission to be present wrt to chat join rate limits

---

<div class="post-metadata">

**Author:** ![unlucky](https://avatars.discourse-cdn.com/v4/letter/u/f07891/32.png) [@unlucky](https://discuss.dev.twitch.com/u/unlucky)\
**Post date:** [December 16, 2025, 1:53pm UTC](https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483/11 "2025-12-16T13:53:37Z")

</div>

> [@Dist](#):
>
> or if using Conduits: [Handling Conduit Events | Twitch Developers](https://dev.twitch.tv/docs/eventsub/handling-conduit-events/#limits)

I saw these words in docs:

 ![image](https://d2opxh93rbxzdn.cloudfront.net/original/3X/4/2/429f80f01126a4d0a7e5d0fe0a396c64ce7c3174.png)

After these words, I thought that only 5 channels were possible.

but I tried to connect to 6 channels and everything worked:

```auto
    await eventsub.listen_channel_chat_message(br_id, mod_id, on_message)
    await eventsub.listen_channel_chat_message(shem_id, mod_id, on_message)
    await eventsub.listen_channel_chat_message(kotaro_id, mod_id, on_message)
    await eventsub.listen_channel_chat_message(kwank_id, mod_id, on_message)
    await eventsub.listen_channel_chat_message(hatiko_id, mod_id, on_message)
    await eventsub.listen_channel_chat_message(gordey_id, mod_id, on_message)

```

Did I misunderstand something?

---

<div class="post-metadata">

**Author:** ![BarryCarlyon](https://d3ovvz0v2ajtpu.cloudfront.net/user_avatar/discuss.dev.twitch.com/barrycarlyon/32/253_2.png) [@BarryCarlyon](https://discuss.dev.twitch.com/u/BarryCarlyon)\
**Post date:** [December 16, 2025, 7:34pm UTC](https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483/12 "2025-12-16T19:34:32Z")

</div>

Shard != Channel/subscription

A conduit has shards (websockets or webhooks), msot bots use one or two shards.

A conduit has subscriptions (as does a webhook or a websocket) (chat channels you want to listen to involves creating a subscription)

---

<div class="post-metadata">

**Author:** ![system](https://d2opxh93rbxzdn.cloudfront.net/original/2X/0/05909729875cce3b53e59d5561c4d23bb14948fc.png) [@system](https://discuss.dev.twitch.com/u/system)\
**Post date:** [January 15, 2026, 7:35pm UTC](https://discuss.dev.twitch.com/t/how-to-send-message-with-eventsub/64483/13 "2026-01-15T19:35:27Z")

</div>

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.
