> ## 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 Monitoraggio delle transazioni: eventi e firma

> Webhook di Didit per le transazioni: trigger, payload e verifica della firma HMAC. 1,000 transazioni al mese incluse, poi $0.001 ciascuna.

Transaction Monitoring invia tre eventi webhook che coprono l'intero ciclo di vita della transazione, più un quarto facoltativo che segnala nuovi suggerimenti di regole. Tutti viaggiano sull'infrastruttura webhook standard di Didit con firma HMAC-SHA256. Consulta il [riferimento webhook](https://docs.didit.me/it/integration/webhooks.md) per destinazioni, firma e test.

## Catalogo degli eventi

| Evento | Quando viene inviato |
| - | - |
| `transaction.created` | È stata creata una nuova transazione e la valutazione iniziale è stata completata. Viene inviato anche per le transazioni in entrata che Didit crea per tuo conto: una [registrazione Travel Rule sunrise](https://docs.didit.me/it/transaction-monitoring/travel-rule.md#registrazione-postuma-del-deposito-sunrise) dopo un deposito, una richiesta `TRP` in entrata oppure un trasferimento sul canale `INTERNAL` da un altro cliente Didit. |
| `transaction.status.updated` | Lo stato della transazione è cambiato, per effetto di una revisione da parte dell'analista, della risoluzione di una sessione di rimedio oppure di un'azione prevista da una regola quando Didit rivaluta le tue regole dopo una transizione Travel Rule (ad esempio dopo una prova di proprietà del wallet). La variazione di stato che applica il tuo `timeout_outcome` quando un trasferimento scade non invia questo evento; reagisci invece a `travel_rule.status.updated` con `EXPIRED`. |
| `travel_rule.status.updated` | Uno scambio [Travel Rule](https://docs.didit.me/it/transaction-monitoring/travel-rule.md) è passato a un nuovo stato, sui trasferimenti in uscita e su quelli in entrata (la componente in entrata sul canale `INTERNAL`, le registrazioni sunrise e le richieste `TRP` in entrata), compresa la transizione `NOT_APPLICABLE` per importi sotto soglia. Per una transazione creata da Didit, questi eventi vengono inviati dopo il suo `transaction.created`. |
| `transaction_rule_suggestions.created` | Un'esecuzione dei suggerimenti di regole ha proposto nuove modifiche alle regole per la tua applicazione. Viene inviato solo se attivi **Webhook** nelle impostazioni di notifica dei suggerimenti di regole, e solo per le esecuzioni che hanno creato dei suggerimenti. |

Gli eventi a livello di entità (`user.status.updated`, `business.status.updated`) possono anche essere inviati come conseguenza dell'attività sulle transazioni: vedi [webhook delle entità](https://docs.didit.me/it/entities/webhooks.md).

> [!NOTE]
>
> Le applicazioni sandbox non inviano webhook `transaction.created` o `transaction.status.updated`. Per testare questi eventi, usa un'applicazione di produzione oppure **API & Webhooks → Try Webhook** nella console. Gli scambi Travel Rule in sandbox inviano webhook `travel_rule.status.updated` con `environment: sandbox`; usa il [Didit Test VASP](https://docs.didit.me/it/transaction-monitoring/travel-rule-sandbox.md) per testare uno scambio prima di andare in produzione.

## Busta

I webhook di Didit usano un corpo **piatto**: i campi dell'evento si trovano al primo livello, accanto ai campi della busta, e non esiste un oggetto `data` annidato.
Ogni consegna contiene:

| Campo | Descrizione |
| - | - |
| `webhook_type` | Il nome dell'evento, ad esempio `transaction.created`. |
| `event_id` | Identificatore stabile dell'evento: lo stesso `event_id` viene riutilizzato nei nuovi tentativi, quindi deduplica su questo campo. |
| `timestamp` / `created_at` | Secondi dall'epoca Unix in cui la consegna è stata inviata. |
| `application_id` | L'applicazione a cui appartiene l'evento. |
| `environment` | `live` oppure `sandbox`. |

## Strutture del payload

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

Entrambi gli eventi condividono un unico payload: un riepilogo sintetico più il blocco [`action_required`](https://docs.didit.me/it/transaction-monitoring/sdk-transaction-submission#azioni-richieste-all’utente) (oppure `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
}
```

Il payload è volutamente essenziale: alla ricezione, recupera il record completo con [`GET /v3/transactions/{transaction_id}/`](https://docs.didit.me/it/management-api/transactions/get). La risposta contiene il dettaglio completo (parti, `rule_runs`, `provider_results`, `travel_rule`, `cost_breakdown`, `decision_reason_code`), incluso il *motivo* della variazione di stato.

### `transaction_rule_suggestions.created`

Questo evento riguarda il tuo insieme di regole, non una singola transazione, quindi non contiene un ID di transazione.
Non contiene mai dati personali: solo ID dei suggerimenti, tipi, titoli e conteggi.

```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 | Descrizione |
| - | - |
| `run_id` | L'esecuzione dei suggerimenti che ha prodotto i nuovi suggerimenti. |
| `open_count` | Quanti suggerimenti risultano aperti per l'applicazione dopo questa esecuzione. |
| `suggestions[]` | I suggerimenti creati da questa esecuzione, ciascuno con `suggestion_id`, `kind`, `title`, `rule_id` (null quando il suggerimento propone una nuova regola) e `expected_impact`. |

I consueti campi della busta (`event_id`, `timestamp`, `application_id`, `environment`) vengono aggiunti al 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"
}
```

Viene inviato a ogni transizione, compresa quella verso `FINISHED`, `CANCELLED` o `EXPIRED`; una volta che un trasferimento raggiunge uno di questi stati, non vengono più inviati eventi Travel Rule per esso.
Un evento può ripetere lo stato del precedente, ad esempio quando un nuovo invio non trova di nuovo alcun canale e il trasferimento resta in `COUNTERPARTY_VASP_NOT_FOUND`.
`rail` può essere `INTERNAL`, `TRP`, `GTR`, `TRUST`, `VERIFYVASP`, `SYGNA`, `EMAIL`, `TEST_VASP` (un trasferimento in sandbox a cui ha risposto il Didit Test VASP, che riporta anche `test_vasp: true` e `test_vasp_scenario`), oppure `""` finché non è selezionato alcun canale (sempre `""` nelle registrazioni sunrise).

Alla creazione di una transazione, di una registrazione sunrise, di una richiesta `TRP` in entrata o della componente in entrata sul canale `INTERNAL`, Didit invia prima `transaction.created` e, dopo, gli eventi `travel_rule.status.updated` della transazione.
Una regola che modifica lo stato della transazione durante questo passaggio può anche inviare `transaction.status.updated` prima di `transaction.created`.
Una registrazione sunrise che si collega a una transazione di deposito già creata da te non invia alcun `transaction.created`.
Ogni webhook viene consegnato e riprovato in modo indipendente, quindi un evento riprovato può arrivare dopo uno successivo: usa `transaction_id` come chiave nel tuo gestore e rileggi la transazione quando ti serve lo stato attuale.

## Esempi di percorsi di stato

### Approvazione semplice

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

### Revisione e poi approvazione

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

### Verifica rafforzata (con passaggio aggiuntivo)

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

### Trasferimento 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)
```

I primi due eventi provengono dalla stessa richiesta, e `transaction.created` viene messo in coda per primo; ogni webhook viene consegnato in modo indipendente, quindi non fare affidamento sull'ordine di arrivo.
Un trasferimento inviato tramite TRUST non riceve mai un evento `COMPLETED`, perché TRUST non restituisce nulla che Didit possa leggere: il trasferimento resta in `AWAITING_COUNTERPARTY` fino alla scadenza per la conferma e poi termina in `EXPIRED`. Con GTR, VerifyVASP e Sygna Bridge, Didit legge la risposta della controparte dalla rete e l'evento segue quella risposta.

## Verifica della firma

Ogni consegna è firmata con HMAC-SHA256 usando il segreto condiviso della destinazione e include tre intestazioni di firma più un timestamp:

| Intestazione | Schema |
| - | - |
| `X-Signature-V2` | HMAC sul JSON canonico (chiavi ordinate, Unicode preservato): **consigliato**. |
| `X-Signature` | HMAC sul JSON canonico con escape ASCII (precedente). |
| `X-Signature-Simple` | HMAC su `"{timestamp}:{session_id}:{status}:{webhook_type}"`: **deprecato**. I payload delle transazioni non contengono `session_id`, quindi la firma viene calcolata su `"{timestamp}::{status}:{webhook_type}"`: non identifica né la transazione né il suo importo o punteggio. Anche un payload `travel_rule.status.updated` non contiene `status`, quindi la firma viene calcolata su `"{timestamp}:::travel_rule.status.updated"`. Verifica con `X-Signature-V2` e consulta [l'avviso di deprecazione](https://docs.didit.me/it/integration/webhooks.md#x-signature-simple-è-deprecato). |
| `X-Timestamp` | Secondi dall'epoca Unix: rifiuta le consegne più vecchie di 5 minuti. |

```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));
}
```

Consulta il [riferimento webhook](https://docs.didit.me/it/integration/webhooks.md#verifica-della-firma) per il codice di verifica completo, incluso il controllo della validità temporale del timestamp.

## Nuovi tentativi e garanzie di consegna

* In caso di `5xx`, `404`, timeout o errore di connessione, Didit ritenta fino a **2 volte**: circa 1 minuto dopo il primo errore, poi altri 4 minuti circa dopo.
* Le consegne ritentate riutilizzano lo stesso `event_id`: usalo per deduplicare nel tuo gestore.
* Dopo l'ultimo errore, la consegna di quell'evento si interrompe e la tua organizzazione viene notificata; le destinazioni non vengono mai disattivate in silenzio.
* Restituisci un `2xx` rapidamente (accoda, poi elabora): ACK lenti rischiano timeout e consegne duplicate.

## Idempotenza

Elabora ogni webhook esattamente una volta memorizzando `event_id` nel tuo archivio di deduplica. La riconsegna dello stesso evento è possibile quando il tuo endpoint invia l'ACK lentamente o si disconnette a metà di una richiesta.

## Sottoscrizione

```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"
    ]
  }'
```

## Passaggi successivi

> [card] [Riferimento webhook](https://docs.didit.me/it/integration/webhooks.md)
>
> Destinazioni webhook, firma, nuovi tentativi.

> [card] [Stati](https://docs.didit.me/it/transaction-monitoring/statuses.md)
>
> Macchina a stati completa.

> [card] [Guida all'integrazione](https://docs.didit.me/it/transaction-monitoring/integration-guide.md)
>
> Gestione dei webhook in produzione.


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