Webhook
传入 Webhook 允许其他系统通过向一个秘密 URL 发送 JSON 来向频道发送消息。传出 Webhook 则相反:它将消息发送到你的 URL,这些消息要么以触发关键字开头,要么由用户在消息菜单中为其选择,然后发布你的回复。
管理 Webhook 的位置
打开右上角的头像,然后选择集成。当前社区的集成页面列出了传入 Webhook和传出 Webhook。每个 Webhook 属于一个社区。
默认情况下,社区管理员和系统管理员可以创建 Webhook。系统管理员可以在系统控制台的权限设置中授予其他角色此权限。系统管理员还可以在系统控制台 › 集成 › 集成管理中关闭每种类型的 Webhook:启用传入 Webhook和启用传出 Webhook默认均为开启状态。

创建传入 Webhook
- 在集成中,选择传入 Webhook,然后选择添加传入 Webhook。
- 填写标题和描述:它们仅在集成页面上显示。
- 选择消息默认发送到的频道。对于私有频道,您必须是该频道的成员。
- 如果发送者不能发布到其他位置,请勾选锁定至此频道。
- 可选地,为其消息设置用户名和个人资料照片 URL(参见名称和图标)。
- 选择保存。复制显示的 URL:
https://chat.example.com/hooks/<key>。
使用 API:POST /api/v4/hooks/incoming,参数为 {"channel_id", "display_name", "description", "username", "icon_url", "channel_locked"};响应中的 id 即为密钥。从命令行:amctl webhook create-incoming --channel <channel id> --user <user> --display-name "CI"。
向传入 Webhook 发送消息
curl -X POST https://chat.example.com/hooks/xgh5ycrx1jf7mbs9w8z4c8e7ho \
-H 'Content-Type: application/json' \
-d '{"text": "Deploy of **api 2.4.0** finished :white_check_mark:"}'
服务器以 200 状态码和 ok 作为响应体进行回复。它还接受 application/x-www-form-urlencoded 格式,其中 JSON 位于 payload 字段中,正如 Slack 的 Webhook 所做的那样,因此为 Slack 制作的工具通常可以不加修改地工作。
| 字段 | 含义 |
|---|---|
text | 以 Markdown 格式编写的消息内容。除非包含 attachments,否则此项为必填。超过服务器消息长度限制的内容将被拆分为多条消息。 |
channel | Webhook 所属社区中的另一个频道,通过名称(ops-alerts 或 #ops-alerts)指定,或者使用 @username 从 Webhook 的所有者发送私信。当 Webhook 被锁定到其所在频道时,此操作将被拒绝。旧名称 town-square 仍然可以到达社区的默认频道,该频道现在称为 general。 |
username, icon_url, icon_emoji | 当服务器允许覆盖时,用于显示的名称和图片。 |
attachments | 消息附件,以卡片形式呈现(见下文)。 |
props | 存储在消息上的额外属性,供读取这些属性的应用使用。 |
type | 自定义帖子类型,例如 custom_poll,用于由应用渲染。以 system_ 开头的类型会被拒绝。 |
root_id | 话题首条消息的 ID,用于在该话题中回复。 |
priority | 消息优先级,例如 {"priority": "urgent"}。 |
silent | 设置为 true 时,发送消息但不触发推送通知。 |
Webhook 发布的每条消息旁边都会带有 机器人 标签,即使它显示在其创建者的名称下,这样人们也能将其与真实用户的消息区分开来。Webhook 消息永远不会触发传出 Webhook。
名称和图标
除非服务器允许覆盖,否则 Webhook 会以创建者的身份发布。在 系统控制台 › 集成 › 集成管理 中有两个设置项(默认均为关闭),可以更改此行为:
- 允许集成覆盖用户名(
ServiceSettings.EnablePostUsernameOverride):将显示 Webhook 的 用户名 或请求中的username;如果两者均未提供,名称将显示为webhook。 - 允许集成覆盖头像(
ServiceSettings.EnablePostIconOverride):将显示 Webhook 的 个人资料照片,或请求中的icon_url或icon_emoji。
请求中的值优先于保存在 Webhook 上的值。
附件
附件在文本下方添加结构化内容。它们遵循 Slack 的消息附件格式:
{
"text": "Build failed on `main`",
"attachments": [{
"fallback": "main #1902 failed",
"color": "#d24b4e",
"author_name": "GitHub Actions",
"author_icon": "https://chat.example.com/static/ci.png",
"title": "main #1902 failed",
"title_link": "https://ci.example.com/runs/1902",
"text": "Commit `0f4d8e2` by Jonas Weber",
"fields": [
{"title": "Branch", "value": "zephyr-3.7", "short": true},
{"title": "Duration", "value": "3m 40s", "short": true}
],
"footer": "Lumen CI",
"ts": 1760000000
}]
}
| 字段 | 含义 |
|---|---|
fallback | 纯文本,用于无法显示卡片的场景,例如通知。 |
color | 颜色条:十六进制颜色值,或 good、warning、danger。 |
pretext | 卡片上方的文本。 |
author_name, author_link, author_icon | 小型标题行。 |
title, title_link | 卡片标题,可选链接。 |
text | 卡片正文,使用 Markdown 格式。 |
fields | {"title", "value", "short"} 的列表;short 字段并排显示。 |
image_url, thumb_url | 下方显示的大图,或侧边显示的小图。 |
footer, footer_icon, ts | 页脚行和时间(Unix 秒)。 |
actions | 按钮和菜单:请参阅 交互式消息。 |
卡片:阶段和图表
Antimatter 为附件添加了两个字段,由默认界面渲染:
stages:以药丸形式显示流水线中的步骤,最多 20 个。每个步骤为{"name", "status"},状态可以是passed、failed、running、pending、skipped或canceled。chart:最近数值的小型折线图,{"values": [2 to 200 numbers], "threshold": number, "label": "…"}。阈值以虚线绘制,表示告警的阈值限制。
{
"attachments": [{
"color": "danger",
"title": "main #1902 failed",
"stages": [
{"name": "build", "status": "failed"},
{"name": "unit", "status": "skipped"},
{"name": "publish", "status": "skipped"}
]
}, {
"color": "warning",
"title": "API latency above 400 ms",
"chart": {"values": [210, 230, 260, 390, 450, 470], "threshold": 400, "label": "p95 latency, ms"}
}]
}
经典界面和其他客户端会忽略这两个字段,因此也请将关键信息放入 text 或 fields 中。
创建传出 Webhook
传出 Webhook 监视社区的公共频道。当消息的第一个词匹配其触发关键字之一,或者有人在消息菜单中为其选择(消息操作)时,它会向你的 URL 发送消息。
- 在集成中,选择传出 Webhook,然后选择添加传出 Webhook。
- 填写标题和描述。
- 选择请求的内容类型:
application/x-www-form-urlencoded或application/json。 - 选择一个频道,输入触发词(一行一个),或者两者都设置。如果只选择频道而不设置触发词,该频道的每条消息都会被发送。如果两者都设置,消息必须同时满足两个条件。
- 在触发时机下,选择第一个完全匹配触发关键字、第一个词以触发关键字开头或从消息菜单。选择从消息菜单时,触发关键字将消失,频道变为可选(如果不指定,该操作将在社区的每个公共频道中提供),并且标题为必填项:它是菜单中该操作的名称。
- 输入回调 URL(每行一个)。每个 URL 都会收到所有匹配的消息。
- 可选地设置用于回复的用户名和个人资料照片。对于来自消息菜单的操作,图标字段将替代个人资料照片:可以是 .png 或 .jpg 图像的 URL,或者在 Antimatter 默认界面中为已上传的图像。菜单中会以灰色显示该图标;如果没有设置,该操作将获得一个中性图标。
- 选择保存并复制显示的令牌。您的服务将使用它来验证请求是否来自您的服务器。
通过 API:POST /api/v4/hooks/outgoing,参数为 {"team_id", "channel_id", "trigger_words", "trigger_when", "callback_urls", "content_type", "display_name"}(trigger_when:0 表示完全匹配,1 表示“以……开头”,2 表示“从消息菜单触发”)。trigger_when 为 2 的 Webhook 不需要触发词,但必须提供 display_name。使用 POST /api/v4/hooks/outgoing/{hook_id}/icon 上传图标(multipart 格式,字段 image:PNG、JPEG 或 GIF,存储为 128 px 的 PNG);其 icon_url 将指向 GET /api/v4/hooks/outgoing/{hook_id}/icon。
ServiceSettings.AllowedUntrustedInternalConnections)中允许其地址。否则,服务器不会调用私有地址。传出请求
Antimatter 发送包含以下字段的 POST 请求,根据内容类型以 JSON 或表单编码格式发送:
{
"token": "6kd1ofo5ojr5zgu5ipfipybgnc",
"team_id": "…",
"team_domain": "lumen-labs",
"channel_id": "…",
"channel_name": "ops-alerts",
"timestamp": 1760010000000,
"user_id": "…",
"user_name": "priya",
"post_id": "…",
"text": "deploy api 2.4.0",
"trigger_word": "deploy",
"file_ids": ""
}
在执行任何操作之前,请先将 token 与 Webhook 的令牌进行比对。在表单编码版本中,timestamp 以秒为单位,而非毫秒。服务器会等待你的响应,时长受 集成请求超时 限制(默认为 30 秒,ServiceSettings.OutgoingIntegrationRequestsTimeout)。
回复出站 Webhook
要发布回复,请以 JSON 格式响应。要发布空内容,请返回空响应体。
{
"text": "Deploying **api 2.4.0** to staging…",
"response_type": "comment",
"username": "deploy-bot",
"icon_url": "https://chat.example.com/static/deploy.png"
}
text和attachments:回复内容。如果两者均未提供,则不会发布任何内容。response_type:comment会将回复发布在触发消息的话题中;否则,回复将发布到频道。username、icon_url:当服务器允许覆盖时显示;否则使用 Webhook 自身的值。props、type、priority:与入站 Webhook 相同。
回复将以 Webhook 创建者的身份发布,如果存在覆盖名称,则使用覆盖后的名称。对于比简短回复更长的内容,请立即响应,稍后通过入站 Webhook 或 API 发布。
消息操作
消息操作是一种出站 Webhook,其 触发时机 设置为 从消息菜单。它不会针对已发布的消息触发。相反,用户会在消息菜单:消息操作(⋯)› 更多操作 中,于 集成 标题下看到它及其图标。经典界面不显示消息操作。选择它会将该条消息发送到回调 URL,并显示一条简短通知,说明已 发送至 该操作名称,或显示您的 ephemeral_text。
- 显示位置:在 Webhook 所在的频道中;如果没有指定频道,则在其所属工作区的所有公共频道中。只有公共频道才支持消息操作。
- 访客 只能看到绑定到其所加入频道的操作,无法看到工作区级别的操作。
- 已归档和只读频道:消息仍然可以发送。归档频道会删除绑定到该频道的 Webhook,因此已归档频道仅保留工作区级别的操作。如果用户无法在该频道中发布回复,该回复将仅对该用户可见。
- 速率限制:每位用户每分钟可执行 10 次消息操作,突发上限为 5 次。超出限制后,服务器将返回
429。 - 私有网络:与其他出站 Webhook 一样,内部地址必须在 允许不受信任的内部连接到 中允许。
请求
负载以 JSON 或表单编码形式发送,具体取决于 Webhook 的内容类型:
{
"token": "6kd1ofo5ojr5zgu5ipfipybgnc",
"trigger": "message_action",
"team_id": "…",
"team_domain": "lumen-labs",
"channel_id": "…",
"channel_name": "ops-alerts",
"timestamp": 1760010000000,
"user_id": "…",
"user_name": "priya",
"post_id": "…",
"post_root_id": "",
"post_create_at": 1760009000000,
"post_user_id": "…",
"post_user_name": "jonas",
"permalink": "https://chat.example.com/lumen-labs/pl/…",
"text": "The staging database is full again",
"files": [{"id": "…", "name": "df.txt", "link": "https://chat.example.com/api/v4/files/…"}],
"priority": "",
"requested_ack": false,
"burn_on_read": false
}
user_id和user_name是选择该操作的用户;post_user_id和post_user_name是消息的作者。- 仅发送该条消息,绝不发送其所属话题中的其他消息。
files仅在选择操作的用户有权下载时列出消息的文件。下载link需要会话或令牌,与 API 中的其他文件相同。- 阅后即焚消息发送时不包含其文本和文件,且
burn_on_read设置为true。 - 在表单编码中,文件以重复的
file_id、file_name和file_link字段形式出现,顺序相同。
响应
响应方式与任何传出 Webhook 相同(参见回复传出 Webhook):text 或 attachments 会在频道中发布回复,而 "response_type": "comment" 会在消息的话题中发布回复。你也可以使用 ephemeral_text(截断至 300 个字符)进行响应:它仅显示给选择该操作的人,并出现在通知中。空响应体不会发布任何内容。
{"ephemeral_text": "Ticket OPS-412 created"}
使用 API
GET /api/v4/channels/{channel_id}/message_actions:返回你在频道中可能选择的操作,包含id、display_name、description和icon_url。不会返回令牌或回调 URL。POST /api/v4/posts/{post_id}/message_actions/{hook_id}:在你可以阅读的消息上运行操作。它返回{"ephemeral_text"}。
当 Webhook 无法工作时
- 来自 Webhook URL 的 501:服务器上的入站 Webhook 已关闭。
- 400 “Failed to decode the payload…”:JSON 无效,或表单字段名称不是
payload。“No text specified.” 表示请求中既没有text也没有attachments。 - 403:Webhook 被锁定到其他频道,或其所有者已无法读取该频道。
- 传出 Webhook 从未触发:频道是私有的、触发词不是第一个单词、消息来自 Webhook,或者 Webhook 设置为从消息菜单触发。
- 消息操作未出现在菜单中:频道是私有的、操作绑定到其他频道、该用户是访客且操作没有关联频道,或者传出 Webhook 已关闭。
- 运行消息操作时返回 404:操作不存在,或者该操作不覆盖该用户在该消息所在频道的权限。429:该用户在一分钟内运行了超过 10 次操作。502:无法访问任何回调 URL(日志中包含原因)。
- 日志:开启 启用 Webhook 调试(
LogSettings.EnableWebhookDebugging),并将日志级别设置为DEBUG,服务器将记录失败的入站请求的负载。失败的出站调用将记录为Outgoing Webhook POST failed。