> ## 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: da equipa para o seu endpoint

> Receba alertas da Didit no seu endpoint HTTPS: revisões, AML, faturação e webhooks falhados. Assinados com HMAC-SHA256, repetidos 24 horas.



Prompt do webhook de alertas
        Copiar o prompt para a área de transferência



      
      

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



      
      

Colar em

Os webhooks de alertas enviam os alertas da sua equipa para um endpoint HTTPS que você controla.
Use-os para alimentar uma uma ferramenta de on-call, uma fila de tickets ou o seu próprio painel de operações.Os webhooks de alertas são separados dos [webhooks de verificação](https://docs.didit.me/pt/integration/webhooks.md).
Os webhooks de verificação enviam os resultados de sessões, entidades e transações para a sua aplicação.
Os webhooks de alertas indicam à sua equipa que algo precisa de atenção: uma verificação à espera de revisão, um pagamento falhado ou um incidente de serviço.
Têm payload, segredo de assinatura, assinatura e calendário de tentativas próprios.## Configurar um destinoConfigura os webhooks de alertas na Business Console, e não através da API.
Os membros que podem gerir as definições da organização podem adicionar, alterar e testar destinos.**1. Adicionar o destino**Em **Definições da organização** > **Notificações da equipa**, adicione um destino **Webhook de alertas** com o URL do seu endpoint.
O URL tem de usar `https://` e resolver para um endereço público.**2. Guardar o segredo de assinatura**A Didit mostra o segredo de assinatura completo, que começa por `whsec_`, apenas uma vez.
Guarde-o agora: depois disso, a consola mostra apenas os últimos quatro caracteres.**3. Encaminhar os alertas para o destino**Adicionar um destino não envia nada para ele.
Adicione o destino às regras de encaminhamento dos alertas que quer que ele receba.**4. Enviar um teste**Clique em **Enviar teste** para enviar um alerta `alert.test` e confirme que o seu endpoint o aceita.Uma organização pode ter até 25 destinos em **Notificações da equipa**.Só a atividade em produção chega a um destino.
Os alertas sobre verificações em sandbox, verificações de empresas, transações e deteções AML nunca são enviados para ele.## Tipos de alerta| `webhook_type` | `severity` | Quando a Didit o envia |
| - | - | - |
| `alert.verification.in_review` | `warning` | Uma verificação foi concluída e sinalizada para revisão manual. |
| `alert.business_verification.in_review` | `warning` | Uma verificação de empresas precisa de revisão manual. |
| `alert.transaction.held` | `warning` | As suas regras de monitorização de transações sinalizaram uma transação para revisão. |
| `alert.aml.monitoring_hit` | `warning` | A monitorização AML contínua encontrou uma nova deteção. |
| `alert.billing.low_balance` | `warning` | O saldo da sua organização desceu abaixo do limiar que definiu. |
| `alert.billing.autorefill_failed` | `critical` | Um carregamento automático do seu saldo falhou porque o cartão foi recusado. |
| `alert.billing.backup_card_charged` | `warning` | Uma cobrança automática foi recusada num cartão e paga com um cartão de reserva. |
| `alert.webhook.delivery_failing` | `warning` | As entregas para um dos seus endpoints de [webhooks de verificação](https://docs.didit.me/pt/integration/webhooks.md) estão a falhar. |
| `alert.service.degraded` | `critical` | Um serviço que a sua organização usa está degradado ou indisponível. |
| `alert.service.recovered` | `info` | Esse serviço voltou ao normal. |
| `alert.test` | `info` | Alguém clicou em **Enviar teste** no destino. |## PayloadCada alerta é um `POST` com um corpo JSON.
A Didit envia o corpo como JSON compacto, com as chaves ordenadas.
Este exemplo está formatado para facilitar a leitura:```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 | Descrição |
| - | - | - |
| `webhook_type` | string | `alert.` seguido do tipo de alerta. Consulte [Tipos de alerta](#tipos-de-alerta). |
| `alert_id` | string | `alt_` seguido de 16 caracteres hexadecimais. Todas as tentativas de reenvio de um alerta trazem o mesmo `alert_id`, por isso use-o para ignorar duplicados. |
| `created_at` | integer | Segundos Unix em que a Didit criou o alerta. Mantém-se igual nas tentativas de reenvio. |
| `severity` | string | `info`, `warning` ou `critical`. |
| `organization` | object | O `id` (UUID) e o `name` da sua organização. |
| `title` | string | Título curto em inglês. |
| `subtitle` | string | Explicação em inglês. Pode estar vazio. |
| `data` | object | O que o alerta descreve. Ver abaixo. |
| `url` | string | Ligação para a página relacionada na consola Didit. |`data` contém sempre:- `event`: a chave do evento, por exemplo `session_in_review`.
- `label` e `value`: o que o alerta descreve, por exemplo `Session` e `#1042`.Alguns tipos de alerta acrescentam chaves a `data`:- `alert.billing.autorefill_failed` acrescenta `paused`, um booleano que é `true` quando o carregamento automático fica agora pausado até atualizar o cartão.
- `alert.service.degraded` e `alert.service.recovered` acrescentam `service`, `status`, `incident_id`, `service_name`, `state`, `affected`, `country_name`, `started_at`, `resolved_at`, `duration_minutes`, `title` e `body`.
  `status` é `degraded` ou `down` em `alert.service.degraded`, e `recovered` em `alert.service.recovered`.
  `service` usa as chaves de serviço de [`GET /v3/status/`](https://docs.didit.me/pt/api-reference/service-status), e `incident_id` corresponde a um `id` de incidente nesse endpoint.Os alertas nunca incluem dados pessoais nem dados de documentos do utilizador final.
Identificam uma sessão, transação ou outro item pelo número e incluem uma ligação para o mesmo na consola.
Ignore os valores de `webhook_type` e as chaves de `data` que não reconhecer, para que as novas não façam falhar o seu endpoint.## Cabeçalhos do pedido| Cabeçalho | Exemplo | Significado |
| - | - | - |
| `Content-Type` | `application/json` | O corpo é JSON. |
| `User-Agent` | `Didit-Alerts/1.0` | Identifica o emissor de alertas da Didit. |
| `X-Timestamp` | `1791884462` | Segundos Unix em que a Didit assinou esta tentativa. É gerado de novo em cada tentativa. |
| `X-Signature-V2` | 64 caracteres hexadecimais | Assinatura HMAC-SHA256 desta tentativa. |## Verificar a assinatura`X-Signature-V2` é o HMAC-SHA256 codificado em hexadecimal da cadeia `<X-Timestamp>.<raw body>`, usando o segredo de assinatura do seu destino como chave.* Utilize o segredo completo exatamente como a Didit o mostrou, incluindo o prefixo `whsec_`.
* Assine os bytes brutos do pedido tal como os recebeu. Analisar o JSON e serializá-lo novamente pode alterar os bytes e fazer falhar a verificação.
* Compare as assinaturas em tempo constante.
* Rejeite um pedido cujo `X-Timestamp` tenha uma diferença superior a 5 minutos em relação ao seu relógio. A Didit assina cada tentativa, incluindo cada nova tentativa, com um timestamp novo, pelo que esta janela nunca rejeita uma nova tentativa legítima.
* Em caso de falha da verificação, responda com `400`. Uma resposta `401`, `403`, `404` ou `410` marca o destino para reconexão (ver [Novas tentativas](#novas-tentativas)).> [!WARNING]
>
> Os webhooks de alerta não são assinados da mesma forma que os webhooks de verificação.
> O cabeçalho também se chama `X-Signature-V2`, mas a cadeia assinada é diferente, e os webhooks de alerta não enviam `X-Signature` nem `X-Signature-Simple`.
> Não reutilize o verificador de webhooks de verificação.### 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
```### Renovar o segredo de assinaturaRenove o segredo no destino em **Notificações da equipa**.
A Didit mostra o novo segredo uma única vez e assina todas as tentativas seguintes com ele, incluindo as novas tentativas de alertas criados antes da renovação.
O segredo antigo deixa de funcionar imediatamente.
Não há período de sobreposição, e nenhum cabeçalho transporta uma assinatura feita com o segredo anterior.
Implemente o novo segredo no seu endpoint assim que o renovar.## Novas tentativasA Didit considera qualquer resposta `2xx` como entregue.
Cada tentativa aguarda até 5 segundos pela sua resposta e não segue redirecionamentos.A Didit volta a tentar enviar um alerta após um tempo limite, uma falha de ligação, uma resposta `5xx`, ou uma resposta `408`, `425` ou `429`.
Faz até 5 novas tentativas, ao longo de cerca de 24 horas:| Nova tentativa | Atraso após a tentativa anterior |
| - | - |
| 1.ª | 1 minuto |
| 2.ª | 5 minutos |
| 3.ª | 30 minutos |
| 4.ª | 3 horas |
| 5.ª | 20 horas e 24 minutos |Qualquer outra resposta é final, incluindo `3xx` e qualquer outra `4xx`.
Um teste enviado com **Enviar teste** tem uma única tentativa e nunca é repetido.Uma resposta `401`, `403`, `404` ou `410` também marca o destino para reconexão: a consola mostra-o com o estado **Voltar a ligar**.
Acontece o mesmo quando um URL resolve para um endereço privado ou bloqueado de outra forma no momento do envio pela Didit.
A Didit continua a enviar novos alertas para o destino, e este volta a ficar ativo após a próxima entrega bem-sucedida ou quando atualizar o respetivo URL.**Notificações recentes**, em **Notificações da equipa**, listam cada entrega com o respetivo resultado, e pode tentar novamente uma entrega falhada a partir daí.## Relacionados- [Webhooks de verificação](https://docs.didit.me/pt/integration/webhooks.md): resultados de sessões, entidades e transações para a sua aplicação.
- [Estado do serviço](https://docs.didit.me/pt/api-reference/service-status.md): o feed público subjacente a `alert.service.degraded` e `alert.service.recovered`.
- [Revisão manual](https://docs.didit.me/pt/console/manual-review.md): o que fazer com uma verificação em análise.


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