Handling Events
You register a handler for each type of event your bot cares about, and the SDK routes each incoming webhook to the matching handler.
How routing works
When a message arrives, the SDK evaluates handlers in this order:
- Direct messages: if the thread is a DM and any
onDirectMessagehandlers are registered, they fire beforeonSubscribedMessage,onNewMention, and pattern handlers. - Subscribed threads: if the thread is subscribed,
onSubscribedMessagefires and no other message handler runs. DMs reach this step only when noonDirectMessagehandlers are registered. - Mentions: if the bot is @-mentioned in an unsubscribed thread,
onNewMentionfires. For backward compatibility, unsubscribed DMs are treated as mentions when noonDirectMessagehandler is registered. - Pattern routing:
onNewMessagehandlers run one after another in registration order. Regex handlers fire when their pattern matches the message text. Handlers registered without a pattern fire for every message that reaches this step, including messages with empty text.
Reactions, slash commands, actions, and modals have their own dedicated routing and are not affected by subscription state.
Handling @-mentions
onNewMention fires when your bot is @-mentioned in a thread it hasn't subscribed to. Most new conversations with your bot start here.
The handler receives a Thread and a Message. Once you call thread.subscribe(), future messages in that thread route to onSubscribedMessage instead. If your bot doesn't need ongoing context, respond without subscribing, as the triage bot below does.
Example: AI assistant with context
Example: Triage bot
Handling subscribed messages
onSubscribedMessage fires for every new message in a non-DM thread your bot has subscribed to. Once subscribed, messages (including @-mentions) route here instead of onNewMention.
If an onDirectMessage handler is registered, DM messages route there before subscription routing. Without a direct handler, subscribed DMs route to onSubscribedMessage.
Messages sent by the bot itself do not trigger this handler. You don't need to filter out your own messages.
Example: Conversational AI with history
See toAiMessages for all options including multi-user name prefixing, message transforms, and attachment handling.
Example: Unsubscribe on keyword
Example: Thread state for multi-step flows
Handling pattern matches
onNewMessage fires for messages in unsubscribed threads that aren't DMs and don't mention the bot. Pass a regex for keyword-triggered responses that don't need an @-mention:
The regex is tested against the message text. To receive every message at this step, including attachment-only messages with empty text, pass only a handler:
When several onNewMessage handlers apply to a message, they run one after another in registration order.
The handler-only form doesn't receive DMs or mentions, even when no handler for them is registered. Use onDirectMessage and onNewMention for those, and onSubscribedMessage for threads the bot has subscribed to.
Example: FAQ auto-responder
Example: Incident detection
Handling reactions
onReaction fires when users add or remove emoji reactions to messages. You can handle all reactions or filter by specific emoji.
ReactionEvent
| Property | Type | Description |
|---|---|---|
emoji | EmojiValue | Normalized emoji name for cross-platform comparison |
rawEmoji | string | Platform-specific emoji string |
added | boolean | true if added, false if removed |
user | Author | The user who reacted |
message | Message (optional) | The message that was reacted to |
thread | Thread | Thread for posting replies |
messageId | string | ID of the message that was reacted to |
threadId | string | Thread ID |
adapter | Adapter | The platform adapter |
raw | unknown | Platform-specific event payload |
Example: Approval workflow
Example: Save to external system
Handling edits and deletes
onMessageUpdated fires when a user edits a message, and onMessageDeleted when one is removed. Both are lifecycle events: they never route through onNewMessage, onNewMention, or onSubscribedMessage, and the concurrency strategies do not apply to them.
previousMessage is the message as it read before the edit, supplied when the platform sends it. Slack does, so you can diff the change rather than only seeing the result. Treat it as optional: a platform that reports edits without the prior text leaves it undefined.
The bot's own edits are filtered out, so a streamed reply that renders through post-and-edit does not call onMessageUpdated back once per delta.
Why the two shapes differ
onMessageUpdated receives (thread, message, previousMessage?) like the other message handlers, because an edit carries a full replacement message.
onMessageDeleted receives a single event instead. A delete has no message: platforms usually report only the id of what was removed, and previousMessage is best-effort. Use chat.thread(event.threadId) when you need a Thread, for example to post a notice in the conversation:
MessageDeletedEvent
| Property | Type | Description |
|---|---|---|
messageId | string | Platform-native id of the deleted message |
threadId | string | Thread the message was in |
channelId | string | Channel the message was in |
platform | string | Adapter name, e.g. "slack" |
previousMessage | Message | undefined | Snapshot, when the platform supplied enough to parse one |
deletedAt | Date | undefined | When the delete happened, when reported |
raw | unknown | Platform-specific delete event |
Only the Slack adapter emits these events today. Registering the handlers on another platform is harmless but nothing will call them. Check the Message edit events and Message delete events rows on each adapter page before relying on them.
Handling interactions
For button clicks, slash commands, and modal forms, see the dedicated guides:
- Slash Commands: handle
/commandinvocations from the message composer. - Actions: handle button clicks and interactive card events.
- Modals: collect structured input through modal dialogs with validation.
Handling Slack-specific events
These handlers are specific to the Slack platform and require the Slack adapter.
Handling agent session stop and title changes
onAgentSessionStopped fires when a user clicks Slack's native stop button.
Chat SDK aborts the active thread.signal and transitions the session out of
processing before invoking your handler.
onAgentSessionTitleChanged fires when a user renames a session in Slack:
Subscribe to agent_session_stopped and agent_session_title_changed in the
Slack app manifest. Model calls should receive thread.signal so stop also
cancels upstream generation.
Handling assistant threads
onAssistantThreadStarted fires when a user opens a new assistant thread in Slack. Use it with the Slack Assistants API to set suggested prompts and status indicators.
The event object includes:
| Property | Type | Description |
|---|---|---|
channelId | string | The assistant thread's channel |
threadTs | string | Thread timestamp |
threadId | string | Thread ID |
userId | string | User who started the thread |
context | object | Assistant context (channelId, teamId, enterpriseId, threadEntryPoint) |
adapter | Adapter | The Slack adapter |
Handling assistant context changes
onAssistantContextChanged fires when the assistant context changes, for example when a user navigates to a different channel while the assistant thread is open.
Handling App Home opens
onAppHomeOpened fires when a user opens your bot's Home tab in Slack. Use it to publish a dynamic view.
The event object includes:
| Property | Type | Description |
|---|---|---|
userId | string | User who opened the Home tab |
channelId | string | Channel context |
tab | string | undefined | The opened tab ("home" or "messages"). Under agentView the event fires for every tab, so branch on this to tell a Home-tab open from the DM-open signal |
entities | AppContextEntity[] | undefined | Folded active-view context (agentView only) |
adapter | Adapter | The Slack adapter |
Handling active-view context (Agent messaging)
Under Slack's Agent messaging experience (agent_view), onAppContextChanged fires when the user's active view changes (opening a channel, DM, or canvas). The event carries normalized entities describing what the user is viewing.
The event object includes:
| Property | Type | Description |
|---|---|---|
channelId | string | The agent conversation channel |
userId | string | The user whose view changed |
entities | AppContextEntity[] | Relevance-ordered active-view entities; empty when the view is empty |
raw | unknown | Platform-specific raw payload |
adapter | Adapter | The Slack adapter |
Each AppContextEntity is one of: { kind: "channel", channelId }, { kind: "canvas", canvasId }, { kind: "list", listId }, { kind: "message", messageTs, channelId }, or { kind: "unknown", type, value } (all with optional teamId/enterpriseId).
Slack also folds this context onto the events the user acts through. onAppHomeOpened events carry the same normalized entities, and so do DM messages. Read the context at message time with getAppContext:
Handling member joined channel
onMemberJoinedChannel fires when a member joins a channel. Slack emits it for every user who joins a channel the bot can see. Teams emits it only when the bot itself is added to a channel or group chat. Use it to post welcome messages or onboard users automatically.
The event object includes:
| Property | Type | Description |
|---|---|---|
adapter | Adapter | The adapter that emitted the event (Slack or Teams) |
channelId | string | The channel that was joined |
userId | string | The user who joined. On Teams this is always the bot (28:<appId>) |
inviterId | string (optional) | The user who invited them |
Installation lifecycle
onInstalled and onUninstalled receive bot installation and removal events.
Currently only the Teams adapter emits them, for personal, group chat, and team
installs.
Events include adapter, the activity id, the action, the platform
conversationId, raw, and the optional actor userId, tenantId, and
locale. The optional channelId is the normalized Chat destination; persist it
and post later with bot.channel(channelId).post().
recordInstallation and removeInstallation are application-owned. For Teams
team installs, use event.raw.channelData.team.id as the installation scope;
the install and removal events can carry different conversationId values.
Include the bot ID and tenant ID in your persistence key.
action is add, add-upgrade, remove, or remove-upgrade. The upgrade
variants mean the app manifest added or removed the bot, so persist and clean
up for both variants. Routine app upgrades do not emit these events.
A Teams team or group chat install also
emits onMemberJoinedChannel, so keep each welcome flow in one handler. Make
side effects idempotent for webhook retries. Handler errors are logged, and
asynchronous work is registered with the webhook's waitUntil.
See Teams installation lifecycle for persistence, reinstall cleanup, and proactive messages.
Read more
Slash Commands
Handle slash command invocations and respond with messages or modals.
Actions
Handle button clicks and interactive card events across platforms.
Modals
Collect structured user input through modal dialogs with text fields, dropdowns, and validation.
Message
Normalized message format with text, AST, author, and metadata.