> ## 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 Alert Tim: Kirim Notifikasi ke Endpoint Anda

> Kirim alert tim Didit ke endpoint HTTPS Anda: peninjauan, Pemantauan AML, penagihan, status. Ditandatangani HMAC-SHA256, dicoba ulang 24 jam.



Prompt Webhook Alert
        Salin prompt ke papan klip



      
      

\<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>



      
      

Tempel ke

Webhook alert mengirim alert tim Anda ke endpoint HTTPS yang Anda miliki.
Gunakan untuk meneruskan alert ke alat on-call, antrean tiket, atau dasbor operasional Anda sendiri.Webhook alert terpisah dari [webhook verifikasi](https://docs.didit.me/id/integration/webhooks.md).
Webhook verifikasi mengirim hasil sesi, entitas, dan transaksi ke aplikasi Anda.
Webhook alert memberi tahu tim Anda bahwa ada sesuatu yang perlu perhatian: verifikasi yang menunggu peninjauan, pembayaran yang gagal, atau insiden layanan.
Webhook ini memiliki payload, rahasia penandatanganan, tanda tangan, dan jadwal coba lagi sendiri.## Siapkan tujuanAnda mengonfigurasi webhook alert di Business Console, bukan melalui API.
Anggota yang dapat mengelola pengaturan organisasi dapat menambahkan, mengubah, dan menguji tujuan.**1. Tambahkan tujuan**Di **Pengaturan organisasi** > **Notifikasi tim**, tambahkan tujuan **Webhook alert** dengan URL endpoint Anda.
URL harus menggunakan `https://` dan mengarah ke alamat publik.**2. Simpan rahasia penandatanganan**Didit hanya menampilkan rahasia penandatanganan lengkap, yang diawali `whsec_`, sekali saja.
Simpan sekarang: setelah itu konsol hanya menampilkan empat karakter terakhirnya.**3. Arahkan alert ke tujuan**Menambahkan tujuan tidak langsung mengirim apa pun ke tujuan tersebut.
Tambahkan tujuan ke aturan perutean untuk alert yang ingin Anda terima.**4. Kirim tes**Klik **Kirim tes** untuk mengirim alert `alert.test`, lalu pastikan endpoint Anda menerimanya.Sebuah organisasi dapat memiliki hingga 25 tujuan di **Notifikasi tim**.Hanya aktivitas produksi yang sampai ke tujuan.
Alert tentang verifikasi sandbox, verifikasi bisnis, transaksi, dan temuan AML tidak pernah dikirim ke tujuan tersebut.## Jenis alert| `webhook_type` | `severity` | Kapan Didit mengirimnya |
| - | - | - |
| `alert.verification.in_review` | `warning` | Sebuah verifikasi selesai dan ditandai untuk peninjauan manual. |
| `alert.business_verification.in_review` | `warning` | Sebuah verifikasi bisnis memerlukan peninjauan manual. |
| `alert.transaction.held` | `warning` | Aturan pemantauan transaksi Anda menandai sebuah transaksi untuk ditinjau. |
| `alert.aml.monitoring_hit` | `warning` | Pemantauan AML berkelanjutan menemukan temuan baru. |
| `alert.billing.low_balance` | `warning` | Saldo organisasi Anda turun di bawah ambang batas yang Anda tetapkan. |
| `alert.billing.autorefill_failed` | `critical` | Isi saldo otomatis gagal karena kartu ditolak. |
| `alert.billing.backup_card_charged` | `warning` | Pembebanan otomatis ditolak pada satu kartu dan dibayar dengan kartu cadangan. |
| `alert.webhook.delivery_failing` | `warning` | Pengiriman ke salah satu endpoint [webhook verifikasi](https://docs.didit.me/id/integration/webhooks.md) Anda gagal. |
| `alert.service.degraded` | `critical` | Sebuah layanan yang digunakan organisasi Anda terdegradasi atau tidak tersedia. |
| `alert.service.recovered` | `info` | Layanan tersebut kembali normal. |
| `alert.test` | `info` | Seseorang mengklik **Kirim tes** pada tujuan tersebut. |## PayloadSetiap alert berupa `POST` dengan body JSON.
Didit mengirim body sebagai JSON ringkas dengan kunci yang diurutkan.
Contoh ini diformat agar mudah dibaca:```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"
}
```| Kolom | Tipe | Deskripsi |
| - | - | - |
| `webhook_type` | string | `alert.` diikuti jenis alert. Lihat [Jenis alert](#jenis-alert). |
| `alert_id` | string | `alt_` diikuti 16 karakter heksadesimal. Setiap kali alert dicoba lagi, alert tersebut membawa `alert_id` yang sama, jadi gunakan untuk mengabaikan duplikat. |
| `created_at` | integer | Detik Unix saat Didit membuat alert. Nilainya tetap sama saat dicoba lagi. |
| `severity` | string | `info`, `warning`, atau `critical`. |
| `organization` | object | `id` (UUID) dan `name` organisasi Anda. |
| `title` | string | Judul singkat dalam bahasa Inggris. |
| `subtitle` | string | Penjelasan dalam bahasa Inggris. Dapat kosong. |
| `data` | object | Hal yang terkait dengan alert. Lihat di bawah. |
| `url` | string | Tautan ke halaman terkait di konsol Didit. |`data` selalu berisi:- `event`: kunci event, misalnya `session_in_review`.
- `label` dan `value`: hal yang dibahas alert, misalnya `Session` dan `#1042`.Beberapa jenis alert menambahkan kunci ke `data`:- `alert.billing.autorefill_failed` menambahkan `paused`, boolean yang bernilai `true` jika isi saldo otomatis kini dijeda hingga Anda memperbarui kartu.
- `alert.service.degraded` dan `alert.service.recovered` menambahkan `service`, `status`, `incident_id`, `service_name`, `state`, `affected`, `country_name`, `started_at`, `resolved_at`, `duration_minutes`, `title`, dan `body`.
  `status` bernilai `degraded` atau `down` pada `alert.service.degraded`, dan `recovered` pada `alert.service.recovered`.
  `service` menggunakan kunci layanan dari [`GET /v3/status/`](https://docs.didit.me/id/api-reference/service-status), dan `incident_id` cocok dengan `id` insiden di sana.Alert tidak pernah menyertakan data pribadi pengguna akhir atau data dokumen.
Alert mengidentifikasi sesi, transaksi, atau item lain dengan nomornya dan menautkannya ke konsol.
Abaikan nilai `webhook_type` dan kunci `data` yang tidak Anda kenali, agar nilai atau kunci baru tidak merusak endpoint Anda.## Header permintaan| Header | Contoh | Arti |
| - | - | - |
| `Content-Type` | `application/json` | Body berupa JSON. |
| `User-Agent` | `Didit-Alerts/1.0` | Mengidentifikasi pengirim alert Didit. |
| `X-Timestamp` | `1791884462` | Detik Unix saat Didit menandatangani percobaan ini. Berbeda pada setiap percobaan. |
| `X-Signature-V2` | 64 karakter heksadesimal | Tanda tangan HMAC-SHA256 dari percobaan ini. |## Verifikasi tanda tangan`X-Signature-V2` adalah HMAC-SHA256 dari string `<X-Timestamp>.<raw body>` yang dienkode dalam heksadesimal, dengan kunci berupa rahasia penandatanganan tujuan webhook Anda.* Gunakan rahasia lengkap persis seperti yang ditampilkan Didit, termasuk awalan `whsec_`.
* Tandatangani byte mentah dari permintaan sebagaimana Anda menerimanya. Mem-parsing JSON lalu men-serialisasi ulang dapat mengubah byte dan membuat pemeriksaan gagal.
* Bandingkan tanda tangan dalam waktu konstan.
* Tolak permintaan yang `X-Timestamp`-nya berselisih lebih dari 5 menit dari jam Anda. Didit menandatangani setiap percobaan, termasuk setiap coba lagi, dengan timestamp baru, sehingga jendela ini tidak pernah menolak coba lagi yang sah.
* Balas pemeriksaan yang gagal dengan `400`. Respons `401`, `403`, `404` atau `410` menandai tujuan untuk dihubungkan ulang (lihat [Coba lagi](#coba-lagi)).> [!WARNING]
>
> Alert webhook tidak ditandatangani seperti webhook verifikasi.
> Header-nya juga bernama `X-Signature-V2`, tetapi string yang ditandatangani berbeda, dan alert webhook tidak mengirim `X-Signature` maupun `X-Signature-Simple`.
> Jangan menggunakan ulang verifikator webhook verifikasi Anda.### 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
```### Rotasi rahasia penandatangananAnda merotasi rahasia pada tujuan di **Notifikasi tim**.
Didit menampilkan rahasia baru sekali saja dan menandatangani setiap percobaan berikutnya dengan rahasia tersebut, termasuk coba lagi untuk alert yang dibuat sebelum rotasi.
Rahasia lama langsung berhenti berfungsi.
Tidak ada periode tumpang tindih, dan tidak ada header yang memuat tanda tangan yang dibuat dengan rahasia sebelumnya.
Terapkan rahasia baru ke endpoint Anda segera setelah Anda merotasinya.## Coba lagiDidit menganggap respons `2xx` apa pun sebagai terkirim.
Setiap percobaan menunggu hingga 5 detik untuk respons Anda dan tidak mengikuti pengalihan.Didit mencoba lagi mengirim alert setelah batas waktu, kegagalan koneksi, respons `5xx`, atau respons `408`, `425` atau `429`.
Didit mencoba hingga 5 kali, selama sekitar 24 jam:| Coba lagi | Jeda setelah percobaan sebelumnya |
| - | - |
| ke-1 | 1 menit |
| ke-2 | 5 menit |
| ke-3 | 30 menit |
| ke-4 | 3 jam |
| ke-5 | 20 jam 24 menit |Respons lainnya bersifat final, termasuk `3xx` dan `4xx` lainnya.
Tes yang dikirim dengan **Kirim tes** mendapat satu percobaan dan tidak pernah dicoba ulang.Respons `401`, `403`, `404` atau `410` juga menandai tujuan untuk dihubungkan ulang: konsol menampilkannya dengan status **Hubungkan ulang**. Hal yang sama berlaku untuk URL yang mengarah ke alamat privat atau alamat lain yang diblokir saat Didit mengirim.
Didit terus mengirim alert baru ke tujuan tersebut, dan tujuan kembali aktif setelah pengiriman berhasil berikutnya atau saat Anda memperbarui URL-nya.**Notifikasi terbaru** di **Notifikasi tim** mencantumkan setiap pengiriman beserta hasilnya, dan Anda dapat mencoba ulang pengiriman yang gagal dari sana.## Terkait- [Webhook verifikasi](https://docs.didit.me/id/integration/webhooks.md): hasil sesi, entitas dan transaksi untuk aplikasi Anda.
- [Status layanan](https://docs.didit.me/id/api-reference/service-status.md): umpan publik di balik `alert.service.degraded` dan `alert.service.recovered`.
- [Peninjauan manual](https://docs.didit.me/id/console/manual-review.md): apa yang harus dilakukan dengan verifikasi dalam peninjauan.


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