跳转到内容

Slack Events API 对话

Slack App 可以通过 Events API 接收消息,再用 Web API 回复。本文推荐本地或无公网入口场景使用 Socket Mode,已有成熟公网网关时使用 HTTP Request URL;核验日期为 2026-09-08,未安装到真实工作区验证。

  • Incoming Webhook 只负责投递,不能替代 Events API 接收用户消息。
  • Socket Mode 与 HTTP Events API 是同一事件体系的两种传输方式,应用逻辑无需写两套。
  • 群聊主路径订阅 app_mention;私信通常还需按实际范围订阅消息事件及相应 Scope。
  • Slack 不自动提供 LLM。规则引擎、业务 API 和 AI 都通过 chat.postMessage 返回。
  1. 有权在目标 Workspace 创建、安装或审批 Slack App。
  2. 准备测试频道,并允许邀请 Bot。
  3. Socket Mode 需要 App-Level Token;HTTP 模式需要公网 HTTPS Request URL 和 Signing Secret。
  4. 服务端能持久化事件去重键和线程上下文。
  1. Slack API Apps 创建 From scratch 应用并选择工作区。
  2. OAuth & Permissions 添加 chat:writeapp_mentions:read;私信和其他消息类型按官方事件页补充最小 Scope。
  3. Event Subscriptions 订阅 Bot Event app_mention
  4. 二选一配置传输:开启 Socket Mode 并创建含 connections:write 的 App-Level Token,或填写 HTTP Request URL。
  5. 安装或重新安装应用,保存 Bot User OAuth Token(通常为 xoxb-)。
  6. 邀请 Bot 进入测试频道,用 @Bot /ping 验证。

HTTP Events API 首次配置会发送 url_verification,服务应返回 challenge。事件回调在 event_callback 内携带 event_id;使用它做事件去重,不要只用消息文本。

HTTP 模式必须用原始请求体、X-Slack-Request-TimestampX-Slack-Signature 按官方 Signing Secrets 规则验签,并拒绝时间戳偏差过大的请求。Socket Mode 由官方 SDK 管理连接确认,但仍要对 event_id 去重。

{
"type": "event_callback",
"event_id": "Ev00000001",
"event": {
"type": "app_mention",
"user": "U0001",
"text": "<@U_BOT> /ping",
"channel": "C0001",
"ts": "1700000000.000001",
"thread_ts": "1700000000.000000"
}
}

会话键建议为 slack:{team_id}:{channel}:{thread_ts || ts}。根消息没有 thread_ts 时,以它自己的 ts 作为线程根。

Terminal window
curl -X POST 'https://slack.com/api/chat.postMessage' \
-H 'Authorization: Bearer SLACK_BOT_TOKEN' \
-H 'Content-Type: application/json; charset=utf-8' \
-d '{"channel":"CHANNEL_ID","thread_ts":"THREAD_TS","text":"pong"}'

收到 app_mention 后移除 Bot mention,优先解析 /ping/status 等命令。用 chat.postMessage 回复原频道,并传入 thread_ts: event.thread_ts || event.ts,让一轮对话固定在原线程中。

HTTP 入口应在 3 秒内确认事件,再异步发送消息。Slack 可能带 X-Slack-Retry-NumX-Slack-Retry-Reason 重试,不能把“已返回 200”当作无需去重。

const threadRoot = event.thread_ts ?? event.ts;
const conversationKey = `slack:${teamId}:${event.channel}:${threadRoot}`;
const answer = await generateReply({
messages: await history.withUserMessage(conversationKey, stripMention(event.text)),
signal: AbortSignal.timeout(20_000),
});
await postMessage({ channel: event.channel, thread_ts: threadRoot, text: answer });

需要读取线程历史时,申请并使用与会话类型匹配的最小 Scope;否则只保存 Bot 自己处理过的上下文。不要假设 Bot 能读取所在频道的所有历史。

  • HTTP 模式先验签再解析 JSON,并校验请求时间戳;Socket Mode 的 Token 也只保存在服务端。
  • 使用 event_id 做事件去重,忽略 bot_idbot_message,防止 Bot 自己触发自己。
  • Bot Token、Signing Secret 和 App-Level Token 分开存储、轮换和授权。
  • Retry-After 处理 429;chat.postMessage 对单频道有特殊速率限制,不应并发刷屏。
  • 对群聊只响应 mention 或明确命令,并对工具调用实施服务端权限检查和提示词注入防护。

收不到 app_mention:检查 Bot Event、app_mentions:read、应用是否重新安装,以及 Bot 是否已进入频道。

not_in_channel:邀请 Bot 进入频道;不要通过扩大 Token 权限掩盖成员关系问题。

消息重复回复:对 event_id 建唯一键,并正确处理 Slack 重试头。

HTTP URL 验证失败url_verification 需要及时返回 challenge;不要在这一步调用模型。