> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# 警报 Webhook：将团队警报推送到你的 endpoint

> Didit 可将团队警报推送到你自己的 HTTPS endpoint，涵盖审核、AML 监控命中、计费、Webhook 失败及服务状态。采用 HMAC-SHA256 签名，并在 24 小时内重试。



警报 Webhook 提示词
        将提示词复制到剪贴板



      
      

\<pre>

\`Add an endpoint that receives Didit alert webhooks (team alerts, not verification results).

\## What to build
A public HTTPS POST endpoint (e.g. /api/didit/alerts) that:
1\. Reads the raw request body as bytes (do NOT parse and re-serialize the JSON before verifying)
2\. Checks that X-Timestamp is within 300 seconds of now
3\. Recomputes X-Signature-V2 = hex(HMAC-SHA256(secret, X-Timestamp + "." + rawBody)), where secret is the FULL signing secret including its "whsec_" prefix
4\. Compares the two signatures in constant time
5\. Parses the JSON, ignores an alert_id it already handled, answers 2xx within 5 seconds, then processes the alert asynchronously
6\. Answers a failed check with 400, not 401 (401, 403, 404 and 410 mark the destination for reconnection)

\## Headers
\- Content-Type: application/json
\- User-Agent: Didit-Alerts/1.0
\- X-Timestamp: Unix seconds, new on every attempt
\- X-Signature-V2: hex HMAC-SHA256 described above
This is NOT the signature scheme of verification webhooks. Alert webhooks send no X-Signature and no X-Signature-Simple.

\## Body (compact JSON, keys sorted)
\{
"alert_id": "alt_<16 hex characters>",  // the same on every retry: deduplicate on it
"created_at": 1791884460,                // Unix seconds when Didit created the alert
"data": { "event": "session_in_review", "label": "Session", "value": "#1042" },
"organization": { "id": "<uuid>", "name": "<your organization>" },
"severity": "info" | "warning" | "critical",
"subtitle": "<English explanation, can be empty>",
"title": "<English headline>",
"url": "<link to the Didit console>",
"webhook_type": "alert.<type>"
}

\## webhook_type values
\- alert.verification.in_review, alert.business_verification.in_review
\- alert.transaction.held, alert.aml.monitoring_hit
\- alert.billing.low_balance, alert.billing.autorefill_failed (data.paused), alert.billing.backup_card_charged
\- alert.webhook.delivery_failing
\- alert.service.degraded (data.status "degraded" or "down"), alert.service.recovered (data.status "recovered")
\- alert.test (sent from the console's Send test button)
Ignore webhook_type values and data keys you do not recognize.

\## Retries
\- 2xx = delivered. Redirects are not followed.
\- Timeout (5 s), connection failure, 5xx, 408, 425 or 429 = retried 5 times, after 1 min, 5 min, 30 min, 3 h and 20 h 24 min (about 24 h).
\- Any other response is final.
\- Test alerts are never retried.

\## Secret rotation
Rotating the secret in the console replaces it at once. There is no overlap and no header signed with the previous secret: deploy the new secret immediately.

Docs: /integration/alert-webhooks`

\</pre>



      
      

粘贴到

警报 Webhook 会将您团队的警报发送到您拥有的 HTTPS endpoint。您可以用它对接值班工具、工单队列或您自己的运营仪表板。警报 Webhook 与[验证 Webhook](https://docs.didit.me/zh/integration/webhooks.md)相互独立。验证 Webhook 会将会话、实体和交易结果发送到您的应用程序。警报 Webhook 则告知您的团队某件事需要关注：等待审核的验证、失败的支付或服务事件。它们拥有各自的 payload、签名密钥、签名和重试计划。## 设置目标您需要在 Business Console 中配置警报 Webhook，而不是通过 API。有权管理组织设置的成员可以添加、修改和测试目标。**1. 添加目标**在 **组织设置** > **团队通知** 中，使用您的 endpoint URL 添加一个 **警报 Webhook** 目标。
URL 必须使用 `https://`，并解析到公共地址。**2. 保存签名密钥**Didit 只会显示一次完整的签名密钥，它以 `whsec_` 开头。
请立即保存：之后控制台只显示其最后四个字符。**3. 将警报路由到该目标**仅添加目标不会向其发送任何内容。
将该目标添加到您希望它接收的警报的路由规则中。**4. 发送测试**点击 **发送测试**，发送一条 `alert.test` 警报，并检查您的 endpoint 是否接受它。一个组织最多可以在 **团队通知** 中添加 25 个目标。只有实时活动会发送到目标。沙盒验证、企业验证、交易和 AML 命中的警报永远不会发送到该目标。## 警报类型| `webhook_type` | `severity` | Didit 发送的时机 |
| - | - | - |
| `alert.verification.in_review` | `warning` | 验证已完成，并被标记为需要人工审核。 |
| `alert.business_verification.in_review` | `warning` | 企业验证需要人工审核。 |
| `alert.transaction.held` | `warning` | 您的交易监控规则将某笔交易标记为需要审核。 |
| `alert.aml.monitoring_hit` | `warning` | 持续 AML 监控发现了新的命中结果。 |
| `alert.billing.low_balance` | `warning` | 您组织的余额低于您设置的阈值。 |
| `alert.billing.autorefill_failed` | `critical` | 由于银行卡被拒绝，余额自动充值失败。 |
| `alert.billing.backup_card_charged` | `warning` | 一张银行卡的自动扣款被拒绝，已改用备用卡完成扣款。 |
| `alert.webhook.delivery_failing` | `warning` | 发送到您的[验证 Webhook](https://docs.didit.me/zh/integration/webhooks.md) endpoint 的投递失败。 |
| `alert.service.degraded` | `critical` | 您的组织所使用的某项服务性能下降或中断。 |
| `alert.service.recovered` | `info` | 该服务已恢复正常。 |
| `alert.test` | `info` | 有人在该目标上点击了 **发送测试**。 |## Payload每条警报都是一个带 JSON 请求体的 `POST` 请求。
Didit 以紧凑格式发送请求体，键按字母顺序排序。
以下示例为便于阅读而进行了格式化：```json theme={null}
{
  "alert_id": "alt_9f2c4e1a7b3d5c80",
  "created_at": 1791884460,
  "data": {
    "event": "session_in_review",
    "label": "Session",
    "value": "#1042"
  },
  "organization": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "Example Org"
  },
  "severity": "warning",
  "subtitle": "A KYC or KYB verification was completed and flagged for manual review.",
  "title": "Verification in review",
  "url": "https://business.didit.me/console/3fa85f64-5717-4562-b3fc-2c963f66afa6/9b2d1c4e-8f3a-4e6b-a1d2-5c7e9f0b3a14/verifications?session-id=1b4e28ba-2fa1-41d2-883f-0016d3cca427",
  "webhook_type": "alert.verification.in_review"
}
```| 字段 | 类型 | 说明 |
| - | - | - |
| `webhook_type` | string | `alert.` 后接警报类型。参见[警报类型](#警报类型)。 |
| `alert_id` | string | `alt_` 后接 16 个十六进制字符。同一警报的每次重试都带有相同的 `alert_id`，因此可据此忽略重复的警报。 |
| `created_at` | integer | Didit 创建该警报时的 Unix 秒数。重试时保持不变。 |
| `severity` | string | `info`、`warning` 或 `critical`。 |
| `organization` | object | 您组织的 `id`（UUID）和 `name`。 |
| `title` | string | 简短的英文标题。 |
| `subtitle` | string | 英文说明。可以为空。 |
| `data` | object | 警报所涉及的内容。见下文。 |
| `url` | string | 指向 Didit 控制台中相关页面的链接。 |`data` 始终包含：- `event`：事件键，例如 `session_in_review`。
- `label` 和 `value`：警报所涉及的内容，例如 `Session` 和 `#1042`。部分警报类型会在 `data` 中添加其他键：- `alert.billing.autorefill_failed` 会添加 `paused`，这是一个布尔值。当自动充值已暂停、需要您更新银行卡后才会恢复时，其值为 `true`。
- `alert.service.degraded` 和 `alert.service.recovered` 会添加 `service`、`status`、`incident_id`、`service_name`、`state`、`affected`、`country_name`、`started_at`、`resolved_at`、`duration_minutes`、`title` 和 `body`。
  对于 `alert.service.degraded`，`status` 为 `degraded` 或 `down`；对于 `alert.service.recovered`，为 `recovered`。
  `service` 使用 [`GET /v3/status/`](https://docs.didit.me/zh/api-reference/service-status) 中的服务键，`incident_id` 与其中某个事件的 `id` 一致。警报永远不包含终端用户的个人数据或证件数据。
它们通过编号来标识会话、交易或其他项目，并在控制台中提供相应链接。
请忽略您不认识的 `webhook_type` 值和 `data` 键，以免新增的值或键导致您的 endpoint 出错。## 请求头| 请求头 | 示例 | 含义 |
| - | - | - |
| `Content-Type` | `application/json` | 请求体为 JSON。 |
| `User-Agent` | `Didit-Alerts/1.0` | 标识 Didit 的警报发送方。 |
| `X-Timestamp` | `1791884462` | Didit 对本次尝试签名时的 Unix 秒数。每次尝试都会更新。 |
| `X-Signature-V2` | 64 个十六进制字符 | 本次尝试的 HMAC-SHA256 签名。 |## 验证签名`X-Signature-V2` 是对字符串 `<X-Timestamp>.<raw body>` 计算的 HMAC-SHA256 十六进制编码值，密钥为你的 Webhook 目标的签名密钥。* 使用 Didit 显示的完整密钥，包括 `whsec_` 前缀。
* 对收到的原始请求字节进行签名。解析 JSON 后再重新序列化可能改变字节内容，导致校验失败。
* 以恒定时间方式比较签名。
* 如果 `X-Timestamp` 与你的时钟相差超过 5 分钟，请拒绝该请求。Didit 会为每次尝试（包括每次重试）使用新的时间戳签名，因此此时间窗口不会拒绝真正的重试。
* 校验失败时返回 `400`。收到 `401`、`403`、`404` 或 `410` 响应会将该 Webhook 目标标记为需要重新连接（参见[重试](#重试)）。> [!WARNING]
>
> 警报 Webhook 的签名方式与验证 Webhook 不同。
> 该请求头同样名为 `X-Signature-V2`，但签名字符串不同，并且警报 Webhook 不发送 `X-Signature` 或 `X-Signature-Simple`。
> 请勿复用你的验证 Webhook 验证器。### Node.js（Express）```javascript theme={null}
const express = require("express");
const crypto = require("crypto");

const app = express();
// The full secret Didit showed when you added the destination, including "whsec_".
const ALERT_SECRET = process.env.DIDIT_ALERT_SECRET;
const TOLERANCE_SECONDS = 300;

// express.raw keeps the body as the exact bytes Didit sent.
app.post("/api/didit/alerts", express.raw({ type: "application/json" }), (req, res) => {
  const timestamp = req.get("X-Timestamp") || "";
  const signature = Buffer.from(req.get("X-Signature-V2") || "", "utf8");

  const now = Math.floor(Date.now() / 1000);
  if (!/^\d+$/.test(timestamp) || Math.abs(now - Number(timestamp)) > TOLERANCE_SECONDS) {
    return res.status(400).send("Stale or missing timestamp");
  }

  const expected = Buffer.from(
    crypto.createHmac("sha256", ALERT_SECRET).update(`${timestamp}.`).update(req.body).digest("hex"),
    "utf8",
  );
  if (signature.length !== expected.length || !crypto.timingSafeEqual(signature, expected)) {
    return res.status(400).send("Invalid signature");
  }

  const alert = JSON.parse(req.body.toString("utf8"));
  res.sendStatus(204); // answer within 5 seconds, then process
  handleAlert(alert); // skip an alert_id you already handled
});
```### Python（Flask）```python theme={null}
import hashlib
import hmac
import json
import os
import time

from flask import Flask, request

app = Flask(__name__)
# The full secret Didit showed when you added the destination, including "whsec_".
ALERT_SECRET = os.environ["DIDIT_ALERT_SECRET"].encode("utf-8")
TOLERANCE_SECONDS = 300


@app.post("/api/didit/alerts")
def didit_alert():
    raw_body = request.get_data()  # the exact bytes Didit sent
    timestamp = request.headers.get("X-Timestamp", "")
    signature = request.headers.get("X-Signature-V2", "")

    try:
        too_old = abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS
    except ValueError:
        too_old = True
    if too_old:
        return "Stale or missing timestamp", 400

    expected = hmac.new(ALERT_SECRET, timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected.encode(), signature.encode()):
        return "Invalid signature", 400

    alert = json.loads(raw_body)
    handle_alert(alert)  # skip an alert_id you already handled; keep this fast
    return "", 204
```### 轮换签名密钥你可以在**团队通知**中为 Webhook 目标轮换密钥。
Didit 只会显示一次新密钥，并使用新密钥签名之后的所有尝试，包括轮换前创建的警报的重试。
旧密钥会立即失效。
不存在过渡期，任何请求头中的签名都不会使用旧密钥生成。
轮换后请立即将新密钥部署到你的 endpoint。## 重试Didit 将任何 `2xx` 响应视为投递成功。
每次尝试最多等待你的响应 5 秒，并且不会跟随重定向。在发生超时、连接失败、`5xx` 响应，或 `408`、`425`、`429` 响应后，Didit 会重试该警报。
最多重试 5 次，历时约 24 小时：| 重试 | 距上一次尝试的延迟 |
| - | - |
| 第 1 次 | 1 分钟 |
| 第 2 次 | 5 分钟 |
| 第 3 次 | 30 分钟 |
| 第 4 次 | 3 小时 |
| 第 5 次 | 20 小时 24 分钟 |其他所有响应均为最终结果，包括 `3xx` 以及其他任何 `4xx` 响应。
使用**发送测试**发送的测试只会尝试一次，且永不重试。收到 `401`、`403`、`404` 或 `410` 响应时，同样会将该 Webhook 目标标记为需要重新连接，控制台会显示**重新连接**状态。
如果 Didit 发送时，某个 URL 解析到私有或其他被封锁的地址，也会出现同样的标记。
Didit 会继续向该 Webhook 目标发送新警报，在下一次成功投递后或你更新其 URL 后，该目标会重新变为活动状态。**团队通知**中的**最近通知**会列出每次投递及其结果，你可以在此处重试失败的投递。## 相关内容- [验证 Webhook](https://docs.didit.me/zh/integration/webhooks.md)：为你的应用提供会话、实体和交易结果。
- [服务状态](https://docs.didit.me/zh/api-reference/service-status.md)：`alert.service.degraded` 和 `alert.service.recovered` 所依据的公开信息源。
- [人工审核](https://docs.didit.me/zh/console/manual-review.md)：处理处于审核中的验证。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.