> ## 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 KYT: eventos de transação e assinatura

> Webhooks de transações da Didit: acionadores, payloads e assinatura HMAC. 1,000 transações/mês incluídas, depois $0.001 cada.

## Catálogo de eventos

A Monitorização de transações emite três eventos de webhook que, em conjunto, cobrem todo o ciclo de vida da transação, mais um quarto evento opcional que anuncia novas sugestões de regras. Todos utilizam a infraestrutura padrão de webhooks da Didit com assinatura HMAC-SHA256. Consulte a [referência de webhooks](https://docs.didit.me/pt/integration/webhooks.md) para destinos, assinatura e testes.

| Evento | Quando é disparado |
| - | - |
| `transaction.created` | Uma nova transação foi criada e a avaliação inicial foi concluída. Também é disparado para as transações de entrada que a Didit cria para si: um [registo sunrise da Travel Rule](https://docs.didit.me/pt/transaction-monitoring/travel-rule.md#registo-sunrise-posterior-ao-depósito) após um depósito, um pedido de consulta `TRP` recebido ou uma transferência na via `INTERNAL` de outro cliente da Didit. |
| `transaction.status.updated` | O estado da transação foi alterado, na sequência de uma análise por um analista, da resolução de uma sessão de remediação ou de uma ação de regra quando a Didit volta a avaliar as suas regras após uma transição da Travel Rule (por exemplo, após uma prova de propriedade da carteira). A alteração de estado que aplica o seu `timeout_outcome` quando uma transferência expira não envia este evento; em vez disso, reaja a `travel_rule.status.updated` com `EXPIRED`. |
| `travel_rule.status.updated` | Uma troca da [Travel Rule](https://docs.didit.me/pt/transaction-monitoring/travel-rule.md) mudou de estado, tanto em transferências de saída como de entrada (a perna de entrada na via `INTERNAL`, os registos sunrise e os pedidos `TRP` de entrada), incluindo a transição `NOT_APPLICABLE` para valores abaixo do limiar. Para uma transação criada pela Didit, estes eventos são enviados depois do seu `transaction.created`. |
| `transaction_rule_suggestions.created` | Uma execução de sugestões de regras propôs alterações às regras da sua aplicação. É disparado apenas se ativar **Webhook** nas definições de notificação de sugestões de regras, e apenas para execuções que criaram sugestões. |

Os eventos ao nível da entidade (`user.status.updated`, `business.status.updated`) também podem ser disparados em consequência de atividade de transações. Consulte [webhooks de entidades](https://docs.didit.me/pt/entities/webhooks.md).

> [!NOTE]
>
> As aplicações sandbox não emitem webhooks `transaction.created` ou `transaction.status.updated`. Para testar esses eventos, use uma aplicação de produção ou **API & Webhooks → Try Webhook** na consola. As trocas sandbox da Travel Rule emitem webhooks `travel_rule.status.updated` com `environment: sandbox`; use o [Didit Test VASP](https://docs.didit.me/pt/transaction-monitoring/travel-rule-sandbox.md) para testar uma troca antes de passar para produção.

## Envelope

Os webhooks da Didit usam um corpo **plano**: os campos do evento ficam ao nível superior, junto aos campos do envelope. Não existe um objeto `data` aninhado.
Cada envio inclui:

| Campo | Descrição |
| - | - |
| `webhook_type` | O nome do evento, por exemplo `transaction.created`. |
| `event_id` | Identificador estável do evento. O mesmo `event_id` é reutilizado nas novas tentativas, por isso elimine duplicados com base nele. |
| `timestamp` / `created_at` | Segundos desde a época Unix em que o envio foi despachado. |
| `application_id` | A aplicação a que o evento pertence. |
| `environment` | `live` ou `sandbox`. |

## Estruturas do payload

### `transaction.created` e `transaction.status.updated`

Ambos os eventos partilham um único payload, composto por um resumo compacto e pelo bloco [`action_required`](https://docs.didit.me/pt/transaction-monitoring/sdk-transaction-submission#ações-necessárias-do-utilizador) (ou `null`):

```json theme={null}
{
  "webhook_type": "transaction.created",
  "event_id": "9c0c8b8a-1111-4222-9333-666666666666",
  "timestamp": 1774970000,
  "created_at": 1774970000,
  "application_id": "11111111-2222-3333-4444-555555555555",
  "environment": "live",
  "transaction_id": "9c7d2a30-...",
  "txn_id": "tx-0001",
  "status": "IN_REVIEW",
  "score": 65,
  "severity": "MEDIUM",
  "amount": "500.00",
  "currency": "EUR",
  "direction": "OUTBOUND",
  "action_required": null
}
```

O payload é intencionalmente reduzido. Ao recebê-lo, obtenha o registo completo com [`GET /v3/transactions/{transaction_id}/`](https://docs.didit.me/pt/management-api/transactions/get). Esse pedido devolve o detalhe completo (intervenientes, `rule_runs`, `provider_results`, `travel_rule`, `cost_breakdown`, `decision_reason_code`), incluindo *o motivo pelo qual* o estado mudou.

### `transaction_rule_suggestions.created`

Este evento diz respeito ao seu conjunto de regras, e não a uma transação específica, por isso não inclui nenhum ID de transação.
Nunca contém dados pessoais: apenas IDs de sugestões, tipos, títulos e contagens.

```json theme={null}
{
  "webhook_type": "transaction_rule_suggestions.created",
  "run_id": "5b2f0c1e-0000-0000-0000-000000000000",
  "open_count": 3,
  "suggestions": [
    {
      "suggestion_id": "9a7d3e22-0000-0000-0000-000000000000",
      "kind": "...",
      "title": "...",
      "rule_id": "...",
      "expected_impact": {}
    }
  ]
}
```

| Campo | Descrição |
| - | - |
| `run_id` | A execução de sugestões que produziu as novas sugestões. |
| `open_count` | Quantas sugestões estão abertas para a aplicação após esta execução. |
| `suggestions[]` | As sugestões criadas por esta execução, cada uma com `suggestion_id`, `kind`, `title`, `rule_id` (null quando a sugestão propõe uma nova regra) e `expected_impact`. |

Os campos habituais do envelope (`event_id`, `timestamp`, `application_id`, `environment`) são acrescentados ao corpo.

### `travel_rule.status.updated`

```json theme={null}
{
  "webhook_type": "travel_rule.status.updated",
  "event_id": "9c0c8b8a-1111-4222-9333-777777777777",
  "timestamp": 1774970000,
  "created_at": 1774970000,
  "application_id": "11111111-2222-3333-4444-555555555555",
  "environment": "live",
  "transaction_id": "22222222-3333-4444-5555-666666666666",
  "txn_id": "trv-2026-07-05-001",
  "travel_rule_status": "COMPLETED",
  "rail": "INTERNAL",
  "direction": "OUTBOUND"
}
```

É disparado em cada transição, incluindo a entrada em `FINISHED`, `CANCELLED` ou `EXPIRED`. Quando uma transferência chega a um desses estados, não são enviados mais eventos da Travel Rule para ela.
Um evento pode repetir o estado do anterior, por exemplo quando um reenvio volta a não encontrar uma via e a transferência permanece em `COUNTERPARTY_VASP_NOT_FOUND`.
`rail` é `INTERNAL`, `TRP`, `GTR`, `TRUST`, `VERIFYVASP`, `SYGNA`, `EMAIL`, `TEST_VASP` (uma transferência sandbox respondida pelo Didit Test VASP, que também inclui `test_vasp: true` e `test_vasp_scenario`), ou `""` enquanto nenhuma via estiver selecionada (sempre `""` nos registos sunrise).

Na criação de uma transação, de um registo sunrise, de um pedido `TRP` de entrada ou da perna de entrada na via `INTERNAL`, a Didit envia primeiro `transaction.created` e depois os eventos `travel_rule.status.updated` da transação.
Uma regra que altere o estado da transação durante esse passo pode também enviar `transaction.status.updated` antes de `transaction.created`.
Um registo sunrise que se associe a uma transação de depósito que já criou não envia `transaction.created`.
Cada webhook é entregue e tentado novamente de forma independente, por isso um evento reenviado pode ainda chegar depois de um evento posterior. Use `transaction_id` como chave do seu processador e releia a transação quando precisar do estado atual.

## Exemplos de percursos de estado

### Aprovação simples

```
transaction.created (APPROVED, score 0)
```

### Revisão seguida de aprovação

```
transaction.created (IN_REVIEW, score 65)
...analyst reviews...
transaction.status.updated (status: APPROVED)
```

### Verificação reforçada

```
transaction.created (AWAITING_USER, action_required.type: verification_session)
...user completes the biometric re-check...
transaction.status.updated (status: APPROVED, action_required: null)
```

### Transferência Travel Rule

```
transaction.created (APPROVED)
travel_rule.status.updated (AWAITING_COUNTERPARTY, rail: EMAIL)
...counterparty accepts the pickup...
travel_rule.status.updated (COMPLETED)
...you broadcast on-chain and PATCH the hash...
travel_rule.status.updated (FINISHED)
```

Os dois primeiros eventos vêm do mesmo pedido, e `transaction.created` é colocado em fila primeiro. Cada webhook é entregue de forma independente, por isso não conte com a ordem de chegada.
Uma transferência enviada pela TRUST nunca recebe um evento `COMPLETED`, porque a TRUST não devolve nada que a Didit possa ler. A transferência fica em `AWAITING_COUNTERPARTY` até ao prazo de confirmação e depois termina em `EXPIRED`. Pelas vias GTR, VerifyVASP e Sygna Bridge, a Didit lê a resposta da contraparte a partir da rede, e o evento segue-se a essa resposta.

## Verificação da assinatura

Cada entrega é assinada com HMAC-SHA256 usando o segredo partilhado do destino e inclui três cabeçalhos de assinatura, bem como um carimbo temporal:

| Cabeçalho | Esquema |
| - | - |
| `X-Signature-V2` | HMAC sobre o JSON canónico (chaves ordenadas, Unicode preservado) - **recomendado**. |
| `X-Signature` | HMAC sobre o JSON canónico com escape ASCII (legado). |
| `X-Signature-Simple` | HMAC sobre `"{timestamp}:{session_id}:{status}:{webhook_type}"` - **obsoleto**. Os payloads de transação não incluem `session_id`, por isso esta assinatura usa `"{timestamp}::{status}:{webhook_type}"`: não identifica a transação, nem o seu montante nem a sua pontuação. Um payload `travel_rule.status.updated` também não inclui `status`, pelo que a assinatura usa `"{timestamp}:::travel_rule.status.updated"`. Verifique com `X-Signature-V2` e consulte [o aviso de descontinuação](https://docs.didit.me/pt/integration/webhooks.md#x-signature-simple-está-obsoleto). |
| `X-Timestamp` | Segundos desde a época Unix; rejeite as entregas com mais de 5 minutos. |

```typescript theme={null}
import { createHmac, timingSafeEqual } from 'crypto';

function verify(rawBody: string, signatureV2: string, secret: string) {
  const canonical = JSON.stringify(
    Object.keys(JSON.parse(rawBody)).sort()
      .reduce((acc, k) => ({ ...acc, [k]: JSON.parse(rawBody)[k] }), {})
  );
  const expected = createHmac('sha256', secret).update(canonical).digest('hex');
  return timingSafeEqual(Buffer.from(signatureV2), Buffer.from(expected));
}
```

Consulte a [referência de webhooks](https://docs.didit.me/pt/integration/webhooks.md#verificação-da-assinatura) para o código completo de verificação, incluindo a verificação de atualidade do carimbo temporal.

## Novas tentativas e garantias de entrega

* Em caso de `5xx`, `404`, timeout ou falha de ligação, a Didit volta a tentar até **2 vezes**: cerca de 1 minuto depois da falha inicial e, depois disso, cerca de 4 minutos mais tarde.
* As entregas repetidas reutilizam o mesmo `event_id`; deduplique-as com base nele no seu processador.
* Após a falha final, a entrega desse evento para e a sua organização é notificada; os destinos nunca são desativados em silêncio.
* Devolva um `2xx` rapidamente (coloque o evento em fila e depois processe-o): confirmações lentas aumentam o risco de timeouts e de entregas duplicadas.

## Idempotência

Processe cada webhook exatamente uma vez, guardando o `event_id` no seu próprio armazenamento de deduplicação. Pode ocorrer nova entrega do mesmo evento se o seu endpoint confirmar lentamente ou se a ligação for interrompida a meio do pedido.

## Subscrição

```bash theme={null}
curl -X POST https://verification.didit.me/v3/webhook/destinations/ \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "TM events",
    "url": "https://yourapp.com/webhooks/didit-tm",
    "subscribed_events": [
      "transaction.created",
      "transaction.status.updated",
      "travel_rule.status.updated"
    ]
  }'
```

## Próximos passos

> [card] [Referência de webhooks](https://docs.didit.me/pt/integration/webhooks.md)
>
> Destinos, assinatura, novas tentativas.

> [card] [Estados](https://docs.didit.me/pt/transaction-monitoring/statuses.md)
>
> Máquina de estados completa.

> [card] [Guia de integração](https://docs.didit.me/pt/transaction-monitoring/integration-guide.md)
>
> Como tratar webhooks em produção.


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