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

> Eventos de webhook de transações da Didit: gatilhos, payloads e verificação de assinatura HMAC. 1,000 transações/mês incluídas, depois $0.001 cada.

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

## Catálogo de eventos

| 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 você: um [registro sunrise da Travel Rule](https://docs.didit.me/pt-br/transaction-monitoring/travel-rule.md#registro-sunrise-após-o-depósito) após um depósito, uma consulta `TRP` de entrada ou uma transferência pelo trilho `INTERNAL` vinda de outro cliente da Didit. |
| `transaction.status.updated` | O status da transação mudou em decorrência de uma revisão de analista, da resolução de uma sessão de remediação ou de uma ação de regra quando a Didit reavalia suas regras após uma transição da Travel Rule (por exemplo, após uma prova de propriedade da carteira). A mudança de status que aplica seu `timeout_outcome` quando uma transferência expira não envia este evento; nesse caso, reaja a `travel_rule.status.updated` com `EXPIRED`. |
| `travel_rule.status.updated` | Uma troca da [Travel Rule](https://docs.didit.me/pt-br/transaction-monitoring/travel-rule.md) mudou de estado, em transferências de saída e de entrada (a perna de entrada pelo trilho `INTERNAL`, registros sunrise e consultas `TRP` de entrada), incluindo a transição `NOT_APPLICABLE` abaixo do limiar. Para uma transação criada pela Didit, esses eventos são enviados depois de `transaction.created`. |
| `transaction_rule_suggestions.created` | Uma execução de sugestões de regras propôs novas alterações de regras para sua aplicação. É disparado somente quando você ativa **Webhook** nas configurações de notificação de sugestões de regras, e somente para execuções que criaram sugestões. |

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

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

## Envelope

Os webhooks da Didit usam um corpo **plano**: os campos do evento ficam no nível superior, ao lado dos campos do envelope, e não há um objeto `data` aninhado.
Toda entrega 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, então use-o para eliminar duplicatas. |
| `timestamp` / `created_at` | Segundos desde a época Unix no momento em que a entrega foi despachada. |
| `application_id` | A aplicação à qual o evento pertence. |
| `environment` | `live` ou `sandbox`. |

## Formatos de payload

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

Os dois eventos compartilham um payload: um resumo compacto mais o bloco [`action_required`](https://docs.didit.me/pt-br/transaction-monitoring/sdk-transaction-submission#ações-exigidas-do-usuário) (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 é propositalmente enxuto. Ao recebê-lo, busque o registro completo com [`GET /v3/transactions/{transaction_id}/`](https://docs.didit.me/pt-br/management-api/transactions/get), que retorna todos os detalhes (partes, `rule_runs`, `provider_results`, `travel_rule`, `cost_breakdown`, `decision_reason_code`), incluindo *por que* o status mudou.

### `transaction_rule_suggestions.created`

Este evento trata do seu conjunto de regras, e não de uma transação específica, por isso não inclui um ID de transação.
Ele nunca contém dados pessoais: apenas IDs, tipos, títulos e contagens das sugestões.

```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 adicionados 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 toda transição, incluindo a passagem para `FINISHED`, `CANCELLED` ou `EXPIRED`. Quando uma transferência chega a um desses status, nenhum outro evento de Travel Rule é enviado para ela.
Um evento pode repetir o status do anterior, por exemplo, quando um reenvio novamente não encontra um trilho e a transferência permanece em `COUNTERPARTY_VASP_NOT_FOUND`.
`rail` é `INTERNAL`, `TRP`, `GTR`, `TRUST`, `VERIFYVASP`, `SYGNA`, `EMAIL`, `TEST_VASP` (uma transferência de sandbox respondida pelo Didit Test VASP, que também inclui `test_vasp: true` e `test_vasp_scenario`) ou `""` enquanto nenhum trilho estiver selecionado (sempre `""` em registros sunrise).

Na criação de uma transação, em um registro sunrise, em uma consulta `TRP` de entrada ou no segmento de entrada pelo trilho `INTERNAL`, a Didit envia primeiro o `transaction.created` e, depois dele, os eventos `travel_rule.status.updated` da transação.
Uma regra que altera o status da transação durante essa etapa também pode enviar `transaction.status.updated` antes de `transaction.created`.
Um registro sunrise vinculado a uma transação de depósito que você já criou não envia `transaction.created`.
Cada webhook é entregue e reenviado de forma independente, então um evento reenviado ainda pode chegar depois de um evento posterior. Use `transaction_id` como chave no seu handler e consulte a transação novamente quando precisar do estado atual.

## Exemplos de jornadas de status

### 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 adicional (step-up)

```
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 com 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 da mesma requisição, e o `transaction.created` entra na fila primeiro. Cada webhook é entregue de forma independente, então não dependa da ordem de chegada.
Uma transferência enviada pelo TRUST nunca recebe um evento `COMPLETED`, porque o TRUST não retorna nada que a Didit consiga ler: ela permanece em `AWAITING_COUNTERPARTY` até o prazo de confirmação e então termina como `EXPIRED`. No GTR, no VerifyVASP e no Sygna Bridge, a Didit lê a resposta da contraparte na rede e o evento reflete essa resposta.

## Verificação de assinatura

Cada entrega é assinada com HMAC-SHA256 usando o segredo compartilhado do destino e traz três cabeçalhos de assinatura, além de um timestamp:

| 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}"` - **descontinuado**. Os payloads de transação não trazem `session_id`, então ele assina `"{timestamp}::{status}:{webhook_type}"`: não identifica a transação, nem o valor nem a pontuação dela. Um payload `travel_rule.status.updated` também não traz `status`, então ele assina `"{timestamp}:::travel_rule.status.updated"`. Faça a verificação com `X-Signature-V2` e consulte [o aviso de descontinuação](https://docs.didit.me/pt-br/integration/webhooks.md#x-signature-simple-está-obsoleto). |
| `X-Timestamp` | Segundos desde a época Unix - rejeite 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-br/integration/webhooks.md#verificação-de-assinatura) para ver o código de verificação completo, incluindo a checagem de validade do timestamp.

## Novas tentativas e garantias de entrega

* Em caso de `5xx`, `404`, timeout ou falha de conexão, a Didit tenta novamente até **2 vezes**: cerca de 1 minuto após a falha inicial e, depois, cerca de 4 minutos depois.
* As entregas repetidas reutilizam o mesmo `event_id` - faça a deduplicação por ele no seu handler.
* Após a última falha, a entrega desse evento é interrompida e sua organização é notificada; os destinos nunca são desativados silenciosamente.
* Retorne um `2xx` rapidamente (enfileire e depois processe) - confirmações lentas correm o risco de timeouts e entregas duplicadas.

## Idempotência

Processe cada webhook exatamente uma vez armazenando o `event_id` no seu próprio repositório de deduplicação. O mesmo evento pode ser entregue novamente quando seu endpoint confirma o recebimento com lentidão ou se desconecta no meio da requisição.

## Inscriçã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-br/integration/webhooks.md)
>
> Destinos, assinatura, novas tentativas.

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

> [card] [Guia de integração](https://docs.didit.me/pt-br/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.