> ## 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.

# Webhooks de alertas: alertas del equipo a tu endpoint

> Didit envía alertas del equipo a tu endpoint HTTPS: revisiones, AML, facturación, webhooks fallidos y estado. Firma HMAC-SHA256, reintentos 24 h.



Prompt de webhook de alertas
        Copiar el prompt al portapapeles



      
      

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



      
      

Pegar en

Los webhooks de alertas envían las alertas de tu equipo a un endpoint HTTPS de tu propiedad.
Úsalos para alimentar una herramienta de guardias, una cola de tickets o tu propio panel de operaciones.Los webhooks de alertas son independientes de los [webhooks de verificación](https://docs.didit.me/es/integration/webhooks.md).
Los webhooks de verificación envían a tu aplicación los resultados de sesiones, entidades y transacciones.
Los webhooks de alertas avisan a tu equipo de que algo requiere atención: una verificación pendiente de revisión, un pago fallido, un incidente del servicio.
Tienen su propio payload, secreto de firma, firma y calendario de reintentos.## Configurar un destinoLos webhooks de alertas se configuran en la Business Console, no a través de la API.
Los miembros que pueden gestionar la configuración de la organización pueden añadir, modificar y probar destinos.**1. Añade el destino**En **Organization settings** > **Team notifications**, añade un destino **Alert webhook** con la URL de tu endpoint.
La URL debe usar `https://` y resolverse a una dirección pública.**2. Guarda el secreto de firma**Didit muestra el secreto de firma completo, que empieza por `whsec_`, una sola vez.
Guárdalo ahora: después, la consola solo muestra sus últimos cuatro caracteres.**3. Dirige las alertas hacia él**Añadir un destino no hace que se le envíe nada todavía.
Añade el destino a las reglas de enrutamiento de las alertas que quieras que reciba.**4. Envía una prueba**Haz clic en **Send test** para enviar una alerta `alert.test` y comprueba que tu endpoint la acepta.Una organización puede tener hasta 25 destinos en **Team notifications**.Solo la actividad en producción llega a un destino.
Las alertas sobre verificaciones en sandbox, verificaciones de empresas, transacciones y coincidencias AML nunca se le envían.## Tipos de alerta| `webhook_type` | `severity` | Cuándo la envía Didit |
| - | - | - |
| `alert.verification.in_review` | `warning` | Una verificación se completó y quedó marcada para revisión manual. |
| `alert.business_verification.in_review` | `warning` | Una verificación de empresa requiere revisión manual. |
| `alert.transaction.held` | `warning` | Tus reglas de monitorización de transacciones marcaron una transacción para revisión. |
| `alert.aml.monitoring_hit` | `warning` | La monitorización continua de AML encontró una nueva coincidencia. |
| `alert.billing.low_balance` | `warning` | El saldo de tu organización bajó del umbral que configuraste. |
| `alert.billing.autorefill_failed` | `critical` | Falló una recarga automática de tu saldo porque la tarjeta fue rechazada. |
| `alert.billing.backup_card_charged` | `warning` | Un cargo automático fue rechazado en una tarjeta y se pagó con una tarjeta de respaldo. |
| `alert.webhook.delivery_failing` | `warning` | Las entregas a uno de tus endpoints de [webhook de verificación](https://docs.didit.me/es/integration/webhooks.md) están fallando. |
| `alert.service.degraded` | `critical` | Un servicio que usa tu organización está degradado o caído. |
| `alert.service.recovered` | `info` | Ese servicio ha vuelto a la normalidad. |
| `alert.test` | `info` | Alguien hizo clic en **Send test** en el destino. |## PayloadCada alerta es un `POST` con un cuerpo JSON.
Didit envía el cuerpo como JSON compacto con las claves ordenadas.
Este ejemplo está formateado para facilitar la lectura:```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"
}
```| Campo | Tipo | Descripción |
| - | - | - |
| `webhook_type` | string | `alert.` seguido del tipo de alerta. Consulta [Tipos de alerta](#tipos-de-alerta). |
| `alert_id` | string | `alt_` seguido de 16 caracteres hexadecimales. Cada reintento de una alerta lleva el mismo `alert_id`, así que úsalo para ignorar duplicados. |
| `created_at` | integer | Segundos Unix en que Didit creó la alerta. No cambia en los reintentos. |
| `severity` | string | `info`, `warning` o `critical`. |
| `organization` | object | El `id` (UUID) y el `name` de tu organización. |
| `title` | string | Titular breve en inglés. |
| `subtitle` | string | Explicación en inglés. Puede estar vacío. |
| `data` | object | El elemento al que se refiere la alerta. Consulta más abajo. |
| `url` | string | Enlace a la página relacionada en la consola de Didit. |`data` siempre contiene:- `event`: la clave del evento, por ejemplo `session_in_review`.
- `label` y `value`: el elemento al que se refiere la alerta, por ejemplo `Session` y `#1042`.Algunos tipos de alerta añaden claves a `data`:- `alert.billing.autorefill_failed` añade `paused`, un booleano que es `true` cuando la recarga automática queda pausada hasta que actualices la tarjeta.
- `alert.service.degraded` y `alert.service.recovered` añaden `service`, `status`, `incident_id`, `service_name`, `state`, `affected`, `country_name`, `started_at`, `resolved_at`, `duration_minutes`, `title` y `body`.
  `status` es `degraded` o `down` en `alert.service.degraded`, y `recovered` en `alert.service.recovered`.
  `service` usa las claves de servicio de [`GET /v3/status/`](https://docs.didit.me/es/api-reference/service-status), e `incident_id` coincide con el `id` de un incidente allí.Las alertas nunca incluyen datos personales del usuario final ni datos de documentos.
Identifican una sesión, una transacción u otro elemento por su número y enlazan a él en la consola.
Ignora los valores de `webhook_type` y las claves de `data` que no reconozcas, para que los nuevos no rompan tu endpoint.## Encabezados de la solicitud| Encabezado | Ejemplo | Significado |
| - | - | - |
| `Content-Type` | `application/json` | El cuerpo es JSON. |
| `User-Agent` | `Didit-Alerts/1.0` | Identifica el emisor de alertas de Didit. |
| `X-Timestamp` | `1791884462` | Segundos Unix en que Didit firmó este intento. Cambia en cada intento. |
| `X-Signature-V2` | 64 caracteres hexadecimales | Firma HMAC-SHA256 de este intento. |## Verificar la firma`X-Signature-V2` es el HMAC-SHA256 codificado en hexadecimal de la cadena `<X-Timestamp>.<raw body>`, calculado con el secreto de firma de tu destino como clave.* Usa el secreto completo tal como te lo mostró Didit, incluido el prefijo `whsec_`.
* Firma los bytes sin procesar de la solicitud tal como los recibiste. Analizar el JSON y volver a serializarlo puede cambiar los bytes y hacer que la comprobación falle.
* Compara las firmas en tiempo constante.
* Rechaza cualquier solicitud cuyo `X-Timestamp` difiera en más de 5 minutos de tu reloj. Didit firma cada intento, incluido cada reintento, con una marca de tiempo nueva, así que esta ventana nunca rechaza un reintento legítimo.
* Responde a una comprobación fallida con `400`. Una respuesta `401`, `403`, `404` o `410` marca el destino para volver a conectarlo (consulta [Reintentos](#reintentos)).> [!WARNING]
>
> Los webhooks de alertas no se firman igual que los webhooks de verificación.
> El encabezado también se llama `X-Signature-V2`, pero la cadena firmada es distinta, y los webhooks de alertas no envían `X-Signature` ni `X-Signature-Simple`.
> No reutilices el verificador de tus webhooks de verificación.### 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
```### Rotar el secreto de firmaRota el secreto en el destino, en **Notificaciones del equipo**.
Didit muestra el nuevo secreto una sola vez y firma con él todos los intentos posteriores, incluidos los reintentos de alertas creadas antes de la rotación.
El secreto anterior deja de funcionar de inmediato.
No hay periodo de transición, y ningún encabezado incluye una firma hecha con el secreto anterior.
Despliega el nuevo secreto en tu endpoint en cuanto lo rotes.## ReintentosDidit considera entregada cualquier respuesta `2xx`.
Cada intento espera hasta 5 segundos para recibir tu respuesta y no sigue redirecciones.Didit reintenta una alerta tras un tiempo de espera agotado, un fallo de conexión, una respuesta `5xx` o una respuesta `408`, `425` o `429`.
La reintenta hasta 5 veces, a lo largo de unas 24 horas:| Reintento | Espera tras el intento anterior |
| - | - |
| 1.º | 1 minuto |
| 2.º | 5 minutos |
| 3.º | 30 minutos |
| 4.º | 3 horas |
| 5.º | 20 horas y 24 minutos |Cualquier otra respuesta es definitiva, incluidas las `3xx` y cualquier otra `4xx`.
Una prueba enviada con **Send test** tiene un único intento y nunca se reintenta.Una respuesta `401`, `403`, `404` o `410` también marca el destino para su reconexión: la consola lo muestra con el estado **Reconnect**.
Lo mismo ocurre con una URL que, en el momento del envío, se resuelve en una dirección privada o bloqueada por cualquier otro motivo.
Didit sigue enviando nuevas alertas al destino, que vuelve a estar activo tras la siguiente entrega exitosa o cuando actualizas su URL.**Recent notifications**, en **Team notifications**, muestra cada entrega con su resultado, y desde ahí puedes reintentar una entrega fallida.## Relacionado- [Webhooks de verificación](https://docs.didit.me/es/integration/webhooks.md): resultados de sesiones, entidades y transacciones para tu aplicación.
- [Estado del servicio](https://docs.didit.me/es/api-reference/service-status.md): el feed público en el que se basan `alert.service.degraded` y `alert.service.recovered`.
- [Revisión manual](https://docs.didit.me/es/console/manual-review.md): qué hacer con una verificación en revisión.


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