> ## 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：チームアラートを自社エンドポイントへ送信

> Diditのチームアラートを自社のHTTPSエンドポイントへ送信できます。対象はレビュー、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エンドポイントに送信します。
オンコールツール、チケットキュー、または独自の運用ダッシュボードにアラートを連携する用途に利用できます。アラートWebhookは[認証Webhook](https://docs.didit.me/ja/integration/webhooks.md)とは別のものです。
認証Webhookは、セッション、エンティティ、取引の結果をアプリケーションに送信します。
アラートWebhookは、レビュー待ちの認証、失敗した支払い、サービス障害など、対応が必要な事象をチームに知らせます。
ペイロード、署名シークレット、署名、再試行スケジュールはいずれも認証Webhookとは別に用意されています。## 送信先を設定するアラートWebhookはAPIではなく、Business Consoleで設定します。
組織設定を管理できるメンバーは、送信先の追加、変更、テストを行えます。**1. 送信先を追加する****組織設定** > **チーム通知**で、エンドポイントのURLを指定して**アラートWebhook**の送信先を追加します。
URLは`https://`を使用し、パブリックアドレスに解決される必要があります。**2. 署名シークレットを保存する**Diditは、`whsec_`で始まる署名シークレットの全体を一度だけ表示します。
この時点で保存してください。以降、コンソールには末尾の4文字しか表示されません。**3. アラートをルーティングする**送信先を追加しただけでは、まだ何も送信されません。
受信したいアラートのルーティングルールに、この送信先を追加します。**4. テストを送信する****テスト送信**をクリックして`alert.test`アラートを送信し、エンドポイントがそれを受け付けることを確認します。1つの組織につき、**チーム通知**に最大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/ja/integration/webhooks.md)エンドポイントへの配信が失敗しているとき。 |
| `alert.service.degraded` | `critical` | 組織が利用しているサービスの性能が低下しているか、停止しているとき。 |
| `alert.service.recovered` | `info` | そのサービスが正常な状態に戻ったとき。 |
| `alert.test` | `info` | 誰かが送信先で**Send test**をクリックしたとき。 |## ペイロードすべてのアラートは、JSONボディを持つ`POST`リクエストです。
Diditは、キーをソートしたコンパクトなJSONとしてボディを送信します。
以下は読みやすく整形した例です。```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桁の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`を追加します。
  `status`は、`alert.service.degraded`では`degraded`または`down`、`alert.service.recovered`では`recovered`です。
  `service`は[`GET /v3/status/`](https://docs.didit.me/ja/api-reference/service-status)のサービスキーを使用し、`incident_id`はそこに含まれるインシデントの`id`と一致します。アラートにエンドユーザーの個人データや書類データが含まれることはありません。
セッション、取引などの項目は番号で識別され、コンソール内の該当ページへのリンクが付きます。
認識できない`webhook_type`の値や`data`のキーは無視してください。そうすることで、新しい値やキーが追加されてもエンドポイントが動作し続けます。## リクエストヘッダー| ヘッダー | 例 | 意味 |
| - | - | - |
| `Content-Type` | `application/json` | ボディがJSONであることを示します。 |
| `User-Agent` | `Didit-Alerts/1.0` | Diditのアラート送信元を識別します。 |
| `X-Timestamp` | `1791884462` | Diditがこの試行に署名した時刻(Unix秒)。試行ごとに新しい値になります。 |
| `X-Signature-V2` | 16進数64文字 | この試行のHMAC-SHA256署名。 |## 署名を検証する`X-Signature-V2`は、文字列`<X-Timestamp>.<raw body>`のHMAC-SHA256を16進数でエンコードした値で、送信先の署名シークレットをキーとして使用します。* シークレットは、Diditが表示したとおり、`whsec_`プレフィックスを含む全体をそのまま使用してください。
* 受信したままの生のリクエストバイト列に対して署名を計算してください。JSONをパースして再度シリアライズすると、バイト列が変わり、検証に失敗することがあります。
* 署名は定数時間で比較してください。
* `X-Timestamp`が自身のサーバーの時刻と5分を超えてずれているリクエストは拒否してください。Diditは再試行を含むすべての試行に新しいタイムスタンプで署名するため、この時間枠によって正当な再試行が拒否されることはありません。
* 検証に失敗した場合は`400`で応答してください。`401`、`403`、`404`、`410`のレスポンスを返すと、送信先は再接続が必要な状態になります（[再試行](#再試行)を参照）。> [!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
```### 署名シークレットをローテーションする署名シークレットのローテーションは、**Team notifications**の送信先で行います。
Diditは新しいシークレットを一度だけ表示し、以降のすべての試行にそのシークレットで署名します。これにはローテーション前に作成されたアラートの再試行も含まれます。
古いシークレットは直ちに使用できなくなります。
移行期間はなく、以前のシークレットで作成された署名を含むヘッダーもありません。
シークレットをローテーションしたら、すぐに新しいシークレットをエンドポイントにデプロイしてください。## 再試行Diditは、`2xx`のレスポンスをすべて配信成功として扱います。
各試行ではレスポンスを最大5秒間待機し、リダイレクトには従いません。Diditは、タイムアウト、接続失敗、`5xx`レスポンス、または`408`、`425`、`429`のレスポンスの後にアラートを再試行します。
再試行は約24時間にわたり、最大5回行われます。| 再試行 | 前回の試行からの待機時間 |
| - | - |
| 1回目 | 1分 |
| 2回目 | 5分 |
| 3回目 | 30分 |
| 4回目 | 3時間 |
| 5回目 | 20時間24分 |`3xx`やその他の`4xx`を含め、それ以外のレスポンスはすべて最終結果となります。
**Send test**で送ったテストは1回だけ試行され、再試行されることはありません。`401`、`403`、`404`、`410`のレスポンスでも送信先は再接続が必要な状態になり、コンソールでは**Reconnect**ステータスとして表示されます。
Diditの送信時に、URLがプライベートアドレスやその他のブロックされているアドレスに解決される場合も同様です。
Diditはその送信先に引き続き新しいアラートを送信し、次に配信が成功するか、URLを更新すると、送信先は再びアクティブになります。**Team notifications**の**Recent notifications**には各配信とその結果が一覧表示され、失敗した配信をそこから再試行できます。## 関連ページ- [認証Webhook](https://docs.didit.me/ja/integration/webhooks.md)：アプリケーションのセッション、エンティティ、取引の結果。
- [サービスステータス](https://docs.didit.me/ja/api-reference/service-status.md)：`alert.service.degraded`と`alert.service.recovered`の元になる公開フィード。
- [手動レビュー](https://docs.didit.me/ja/console/manual-review.md)：レビュー中の認証への対応方法。


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