主题
XHIM 前置策略与事后业务 Webhook
XHIM 提供两条用途不同的业务集成链路:
| 类型 | 时机 | 可影响原操作 | 可靠性语义 | 版本 |
|---|---|---|---|---|
| 事务 Outbox 事后 Webhook | 数据提交成功后 | 否 | HMAC、重试、Dead Letter、管理后台补投 | Business/Enterprise |
| 同步前置策略 Webhook | 数据提交前 | 可允许、拒绝或安全改写 | 有界超时,显式 fail-open/fail-closed | Enterprise/OEM |
不要用事后 Webhook 做内容拦截,因为响应返回时消息已经提交;也不要用前置策略 同步耗时业务数据,它会增加用户操作延迟。
1. 配置
dotenv
XHIM_PRODUCT_EDITION=enterprise
XHIM_POLICY_WEBHOOK_URL=https://business.example.com/xhim/policy
XHIM_POLICY_WEBHOOK_KEY_ID=policy-2026-01
XHIM_POLICY_WEBHOOK_SECRET_BASE64=<至少32字节随机密钥的Base64>
XHIM_POLICY_WEBHOOK_REQUEST_TIMEOUT=5s
XHIM_POLICY_WEBHOOK_FAILURE_MODE=deny
XHIM_POLICY_WEBHOOK_EVENTS=message.send.before,group.create.before事件列表留空表示启用全部支持事件:
text
message.send.before
message.mutate.before
friend.request.before
group.create.before
group.members.change.before
group.join.request.before
group.governance.beforefailure_mode=deny 在超时、网络失败、非 2xx、超大或非法响应时返回 policy_unavailable(HTTP 503);allow 会继续原操作,适合更重视可用性的业务。 业务明确拒绝返回 policy_rejected(HTTP 403)。
2. 请求签名
服务端发送 JSON POST,并附带:
text
X-XHIM-Policy-Timestamp: 1786074000
X-XHIM-Policy-Key-ID: policy-2026-01
X-XHIM-Policy-Version: 1.0
X-XHIM-Policy-Request-ID: policy-01...
X-XHIM-Policy-Signature: sha256=<hex>签名原文:
text
HMAC-SHA256(secret, timestamp + "." + raw_request_body)接收方必须先按 Key ID 找密钥,校验原始 Body 的常量时间 HMAC,再校验时间窗与 Request ID 幂等。不要先反序列化再重新编码后验签。
消息请求示例:
json
{
"version": "1.0",
"request_id": "policy-01...",
"app_id": "com.customer.chat",
"type": "message.send.before",
"requested_at": "2026-08-07T09:00:00Z",
"actor": {
"user_id": "user-internal-id",
"account_id": "account-id",
"device_id": "ios-device",
"platform": "ios"
},
"trace": {
"operation_id": "send-20260807-1",
"trace_id": "trace-1",
"client_id": "client-message-1"
},
"proposal": {
"conversation_id": "direct-...",
"client_message_id": "client-message-1",
"content_type": "text/plain",
"content_version": 1,
"payload_base64": "5b6e5aW977yM",
"fallback_text": "原始摘要"
}
}3. 响应
允许:
json
{"decision":"allow"}拒绝:
json
{
"decision": "deny",
"code": "content_blocked",
"reason": "命中客户内容策略"
}安全改写文字/消息 Payload:
json
{
"decision": "allow",
"patch": {
"payload_base64": "5bey5pWP5YaF5a65XQ==",
"fallback_text": "[敏感内容]"
}
}服务端只接受每类事件明确声明的 Patch 字段,出现未知字段会按无效策略响应处理。 改写后的命令会重新计算 Payload Hash、重新解析媒体和 @ 受众,并重新执行领域 校验与权限校验。策略服务不能修改 App、操作者、会话、幂等 ID、revision 或消息 类型来绕过服务端权限。
| 事件 | 允许改写字段 |
|---|---|
message.send.before | payload_base64、fallback_text |
message.mutate.before | 仅文字编辑可改 payload_base64、fallback_text;撤回不可改内容 |
friend.request.before | introduction |
group.create.before | title、member_user_ids、initial_administrator_user_ids |
group.members.change.before | add_user_ids、remove_user_ids |
group.join.request.before | introduction |
group.governance.before | 群资料、入群/成员可见性等对应的安全字段 |
4. 生产要求
- URL 使用 HTTPS,禁止 URL 用户名密码、Query 和 Fragment;
- 策略服务 P99 必须明显低于配置超时,不能同步调用慢速 OA 页面;
- 对内容审核设置独立熔断、限流和可观测性;
- fail-open 必须在客户安全评审中明确记录;
- 拒绝原因可记录到业务审计,但不向普通客户端暴露敏感规则细节;
- Key 轮换期间同时接受新旧 Key ID,完成重叠窗口后再退役旧密钥。
5. 批量迁移的事后事件边界
批量迁移不会把每一个导入对象伪装成一次日常用户操作:
- 好友关系导入只产生一个
friendships.importedafter-event; - 历史消息导入只产生一个
message.history.importedafter-event,不产生 逐消息message.sentafter-event。
message.history.imported 的 resource 只包含导入作业 ID、会话 ID、最后服务端 序列和导入数量;trace 只包含 trace_id 与 operation_id。信封不含 消息 payload、fallback、sender 列表、source system 或 source message ID。 接收方应以 event ID 幂等消费这一个聚合事件,不应按导入数量期待多次回调。
6. Push 与 Presence 事后事件
Push 注册、客户端停用、管理员停用和 Provider 判定 Token 失效,均在设备状态 事务内追加耐久 after-event:push.device.registered 或 push.device.disabled。信封只投影用户 ID、设备 ID、平台、启用状态和容量结果; Provider Token、环境/区域参数与 Provider 原始错误不会进入 Webhook。
presence.changed 只在用户的聚合 online|away|offline 状态或已排序活跃平台集合 发生变化时追加。普通续租心跳不产生业务事件。事件不包含 Account ID、Session ID、 TTL、ExpiresAt 或客户端心跳参数;接收方以 event ID 幂等,并按 resource.sequence 处理顺序或重放。