故障Webhook推送¶
Webhook 用于将故障的关键变化推送到第三方系统。您可以将故障信息接入 ITSM、自动化平台或内部业务系统,由第三方根据收到的数据继续创建工单、同步状态或触发自动化流程。
在故障中心 > 配置管理 > Webhook中可创建和管理 Webhook。
使用前提¶
请先准备一个能够接收 POST 请求的第三方地址。Webhook 支持 HTTP 和 HTTPS 地址;建议在生产环境使用 HTTPS。出于安全考虑,不能配置内网、回环或本机地址。
配置 Webhook¶
- 在Webhook页面点击新建 Webhook;
- 填写名称和第三方接收 URL;
- 选择需要自动推送的故障事件: | 事件 | 说明 | | --- | --- | | 故障创建 | 新故障创建时推送。 | | 状态变化 | 故障状态发生变化时推送。 | | 等级变更 | 故障等级发生变化时推送。 | | 处理人变化 | 故障处理人发生变化时推送。 | | 聚合变更 | 聚合故障关联事件发生变化时推送。仅对通过故障聚合规则生成的故障生效。 |
- 点击生成 Token。Token 仅由 观测云 平台生成,请立即复制并安全保存到第三方接收服务的密钥配置中;
- 设置启用状态并保存。
自动触发事件可以不选择。Webhook 启用后,即使未配置自动触发事件,仍可从故障详情页手动发送。
注意
- Token 是 Webhook 的认证密钥。生成后平台仅展示一次明文,后续页面仅显示脱敏值;重新生成 Token 会使旧 Token 立即失效;
- Webhook 投递失败不会影响故障的创建、更新、恢复和关闭;
- Webhook 使用固定请求方式、鉴权方式和超时时间,无需额外配置。
测试发送¶
保存 Webhook 后,可在该 Webhook 的操作项中点击测试并选择事件类型。系统会按正式请求格式发送一条测试数据,Payload 中的 isTest 为 true。
测试结果可在发送历史中查看。测试发送不影响真实故障,也不会计入正式发送统计。
手动发送¶
在故障详情页点击发送 Webhook,可将故障的最新快照立即发送至一个已启用的 Webhook。选择 Webhook 并填写 1~1000 个字符的发送说明后即可发送。手动发送具有以下特点:
- 支持所有故障状态,不受该 Webhook 自动触发事件配置和聚合窗口影响;
- 每次只能选择一个 Webhook;同一成员在 5 秒内不能对同一故障和同一 Webhook 重复提交;
- 返回
2xx状态码视为成功;网络异常、URL 无效、响应超时或非2xx状态码视为失败; - 发送结果会写入发送历史,事件类型显示为“手动触发”,发送类型仍为“正式”;
- 发送失败不会自动重试。如需再次发送,请回到故障详情页重新触发。
手动发送的 Payload 沿用请求协议和故障快照结构,并增加以下信息:
| 字段 | 说明 |
|---|---|
eventTypes |
包含 incident.manual_triggered。 |
triggerSource |
固定为 manual。 |
manualContext |
手动发送上下文,包含操作人、发送说明和操作时间。 |
手动发送不会在故障协作记录或活动时间线中新增记录。
请求协议¶
系统以 POST 方式发送 JSON 数据,请求超时时间为 5 秒,不跟随重定向。接收服务返回任意 2xx 状态码即视为投递成功。
请求 Header¶
| Header | 说明 |
|---|---|
Content-Type |
固定为 application/json。 |
Authorization |
固定格式为 Bearer <Token>。 |
X-Incidents-Event-Id |
本次投递的唯一标识,可用于接收端幂等处理。 |
X-Incidents-Timestamp |
Unix 秒级时间戳。 |
X-Incidents-Signature |
HMAC-SHA256 签名,格式为 v1=<signature>。 |
Payload 示例¶
{
"eventId": "evt_01JXYZ...",
"eventTypes": [
"incident.created",
"incident.status_changed"
],
"eventTime": 1783000000,
"workspaceUUID": "wksp_xxx",
"incident": {
"uuid": "incident_xxx",
"name": "API service unavailable",
"level": "level_1",
"incidentsStatus": "working",
"resourceType": "incident_aggregation",
"aggregationRuleUUID": "rule_xxx",
"aggregationRuleNameSnapshot": "生产环境服务聚合",
"eventRelations": [
{
"df_fault_id": "fault_xxx",
"status": "critical",
"aggregationValues": {
"df_workspace_name": "production",
"df_dimension_tags.host": "web-01"
}
}
]
},
"isTest": false
}
字段说明:
| 字段 | 说明 |
|---|---|
eventId |
一次 Webhook 投递批次的唯一 ID,不是原始事件 ID。 |
eventTypes |
本次批次包含的故障变化类型。 |
eventTime |
本次投递的 Unix 秒级时间戳。 |
workspaceUUID |
故障所属工作空间 UUID。 |
incident |
故障当前完整快照,具体字段见下文。resourceContent 不会推送。 |
isTest |
是否为测试发送。 |
incident 字段说明¶
incident 为发送时刻的故障快照。它会随故障的等级、状态、处理人和关联事件变化而更新。
| 字段 | 类型 | 说明 |
|---|---|---|
id |
integer | 故障记录的内部数字 ID。 |
uuid |
string | 故障 UUID,格式为 incident_xxx,建议作为第三方故障主键。 |
workspaceUUID |
string | 故障所属工作空间 UUID。 |
name |
string | 故障标题。 |
level |
string | 当前故障等级 UUID 或等级标识。 |
description |
string | 故障描述。 |
incidentsStatus |
string | 故障业务状态。常见值包括 open(待分配)、working(处理中)、resolved(已解决)和 closed(已关闭)。 |
assigner |
array | 当前处理人账号 UUID 列表;未分配时为空数组。 |
statusTime |
object | 各故障业务状态对应的时间戳记录。 |
statusChangeTime |
integer | 最近一次故障业务状态变更的 Unix 秒级时间戳。 |
cumulativeTime |
integer | 故障累计持续时长,单位为秒。 |
eventCount |
integer | 当前故障累计关联的事件数量。 |
eventUpdateAt |
integer | 最近一次关联事件更新的 Unix 秒级时间戳。 |
eventRelations |
array | 当前关联事件快照,字段说明见下文。 |
source |
string | 故障来源标识。 |
resourceCategory |
string | 来源资源分类。 |
resourceType |
string | 来源资源类型。聚合规则生成的故障固定为 incident_aggregation。 |
resourceUUID |
string | 来源资源 UUID。 |
resourceUrl |
string | 来源资源的访问地址;无地址时为空。 |
resourceIdentity |
string | 用于识别同一来源故障的资源标识。 |
aggregationRuleUUID |
string | 生成聚合故障的规则 UUID。仅聚合故障返回。 |
aggregationRuleNameSnapshot |
string | 聚合规则在故障创建时记录的名称快照。仅聚合故障返回;规则改名或删除后可用于识别历史来源。 |
dimensionTag |
object | 故障关联的维度标签。 |
dtHost |
string | 提取出的主机维度值。 |
dtService |
string | 提取出的服务维度值。 |
dtResource |
string | 提取出的资源维度值。 |
dtPodName |
string | 提取出的 Pod 名称维度值。 |
dtAppName |
string | 提取出的应用名称维度值。 |
dtAppId |
string | 提取出的应用 ID 维度值。 |
dtEnv |
string | 提取出的环境维度值。 |
dtUrl |
string | 提取出的 URL 维度值。 |
extend |
object | 故障扩展信息。该对象会随产品能力演进扩展,第三方应忽略不识别的字段。 |
status |
integer | 故障记录状态,用于标识记录是否有效;不要将其作为故障业务状态使用。 |
creator |
string | 创建该故障记录的账号 UUID 或系统标识。 |
updator |
string | 最近更新该故障记录的账号 UUID 或系统标识。 |
createAt |
integer | 故障记录创建时间,Unix 秒级时间戳。 |
updateAt |
integer | 故障记录最近更新时间,Unix 秒级时间戳。 |
deleteAt |
integer | 故障记录删除时间,未删除时通常为负值。 |
eventRelations 中每一项表示当前关联的一个事件。稳定返回的字段包括:
| 字段 | 类型 | 说明 |
|---|---|---|
df_fault_id |
string | 关联事件的故障 ID。 |
status |
string | 关联事件当前状态,例如 critical 或 ok。 |
aggregationValues |
object | 聚合键值快照。键为规则保存的完整字段路径,值为生成聚合身份时使用的标准化值;仅聚合故障关联事件返回。 |
不同事件来源可携带额外字段。第三方应按 df_fault_id 识别关联事件,并忽略未识别的扩展字段。对于历史聚合故障,aggregationValues 可能缺失或为空,接收端应兼容处理。聚合故障的关联事件快照或聚合键值发生变化时,会触发“聚合变更”事件。
验证请求来源¶
第三方接收服务应同时校验 Token、时间戳和签名。
签名原文为:
其中 rawRequestBody 是接收到的原始请求字节,不应先格式化或重新序列化 JSON。推荐仅接受当前时间前后 5 分钟内的请求,并按 eventId 去重。
import hashlib
import hmac
import time
def verify_webhook(request, token):
raw_body = request.get_data(cache=True)
timestamp = request.headers.get("X-Incidents-Timestamp", "")
event_id = request.headers.get("X-Incidents-Event-Id", "")
signature = request.headers.get("X-Incidents-Signature", "")
authorization = request.headers.get("Authorization", "")
if not hmac.compare_digest(authorization, f"Bearer {token}"):
return False
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
return False
message = f"{timestamp}.{event_id}.".encode("utf-8") + raw_body
expected = "v1=" + hmac.new(
token.encode("utf-8"), message, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
boolean verify(byte[] rawBody, String timestamp, String eventId,
String signature, String authorization, String token) throws Exception {
if (!MessageDigest.isEqual(
authorization.getBytes(StandardCharsets.UTF_8),
("Bearer " + token).getBytes(StandardCharsets.UTF_8))) return false;
byte[] prefix = (timestamp + "." + eventId + ".").getBytes(StandardCharsets.UTF_8);
byte[] content = new byte[prefix.length + rawBody.length];
System.arraycopy(prefix, 0, content, 0, prefix.length);
System.arraycopy(rawBody, 0, content, prefix.length, rawBody.length);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(token.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String expected = "v1=" + java.util.HexFormat.of().formatHex(mac.doFinal(content));
return MessageDigest.isEqual(expected.getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8));
}
import (
"crypto/hmac"
"crypto/sha256"
"fmt"
)
func verifyWebhook(rawBody []byte, timestamp, eventID, signature, authorization, token string) bool {
if !hmac.Equal([]byte(authorization), []byte("Bearer "+token)) {
return false
}
content := append([]byte(timestamp+"."+eventID+"."), rawBody...)
mac := hmac.New(sha256.New, []byte(token))
mac.Write(content)
expected := fmt.Sprintf("v1=%x", mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}
投递与发送历史¶
同一个 Webhook 对同一故障在短时间内连续产生多个变化时,系统会合并为一次投递,并在 Payload 的 eventTypes 中返回本次包含的变化类型。Payload 始终以发送时的最新故障快照为准。
系统不自动重试。您可以在发送历史中按发送状态、故障 UUID 或事件类型查看投递结果;失败记录会展示响应码和错误详情,便于排查第三方接收服务。自动推送失败后,如需再次发送,可前往对应故障详情页手动触发。