> ## 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: esdeveniments i verificació de la signatura

> Webhooks de Didit per a transaccions: activadors, payload i signatura HMAC. 1,000 transaccions al mes incloses, després $0.001 cadascuna.

Transaction Monitoring emet tres esdeveniments de webhook que, junts, cobreixen tot el cicle de vida de la transacció, i un quart opcional que anuncia nous suggeriments de regles. Tots fan servir la infraestructura estàndard de webhooks de Didit amb signatura HMAC-SHA256: consulta la [referència de webhooks](https://docs.didit.me/ca/integration/webhooks.md) per a destinacions, signatura i proves.

## Catàleg d'esdeveniments

| Esdeveniment | Quan s'activa |
| - | - |
| `transaction.created` | S'ha creat una transacció nova i s'ha completat l'avaluació inicial. També s'activa per a les transaccions entrants que Didit crea per a tu: un [registre sunrise de la Travel Rule](https://docs.didit.me/ca/transaction-monitoring/travel-rule.md#registre-posterior-al-dipòsit) posterior a un dipòsit, una consulta `TRP` entrant o una transferència de la xarxa `INTERNAL` d'un altre client de Didit. |
| `transaction.status.updated` | L'estat de la transacció ha canviat, ja sigui per la revisió d'un analista, per la resolució d'una sessió de remediació o per una acció d'una regla quan Didit torna a avaluar les teves regles després d'una transició de la Travel Rule (per exemple, després d'una prova de titularitat d'una cartera). El canvi d'estat que aplica el teu `timeout_outcome` quan caduca una transferència no envia aquest esdeveniment; reacciona a `travel_rule.status.updated` amb `EXPIRED` en el seu lloc. |
| `travel_rule.status.updated` | Un intercanvi de la [Travel Rule](https://docs.didit.me/ca/transaction-monitoring/travel-rule.md) ha fet una transició, tant en transferències sortints com entrants (el tram entrant de la xarxa `INTERNAL`, els registres sunrise i les consultes `TRP` entrants), inclosa la transició `NOT_APPLICABLE` per sota del llindar. Per a una transacció que Didit crea, aquests esdeveniments s'envien després del seu `transaction.created`. |
| `transaction_rule_suggestions.created` | Una execució de suggeriments de regles ha proposat canvis nous a les regles de la teva aplicació. S'activa només si actives **Webhook** a la configuració de notificacions de suggeriments de regles, i només per a les execucions que han creat suggeriments. |

Els esdeveniments a nivell d'entitat (`user.status.updated`, `business.status.updated`) també poden activar-se com a conseqüència de l'activitat de transaccions; consulta [webhooks d'entitats](https://docs.didit.me/ca/entities/webhooks.md).

> [!NOTE]
>
> Les aplicacions sandbox no emeten webhooks `transaction.created` ni `transaction.status.updated`. Per provar aquests esdeveniments, utilitza una aplicació de producció o **API & Webhooks → Try Webhook** a la consola. Els intercanvis sandbox de la Travel Rule sí que emeten webhooks `travel_rule.status.updated` amb `environment: sandbox`; utilitza el [Didit Test VASP](https://docs.didit.me/ca/transaction-monitoring/travel-rule-sandbox.md) per provar un intercanvi abans de passar a producció.

## Embolcall

Els webhooks de Didit fan servir un cos **pla**: els camps de l'esdeveniment es troben al nivell superior, al costat dels camps de l'embolcall, i no hi ha cap objecte `data` imbricat.
Cada lliurament inclou:

| Camp | Descripció |
| - | - |
| `webhook_type` | El nom de l'esdeveniment, per exemple `transaction.created`. |
| `event_id` | Identificador estable de l'esdeveniment: el mateix `event_id` es reutilitza en els reintents, així que desduplica'l per aquest camp. |
| `timestamp` / `created_at` | Segons de l'època Unix en què s'ha enviat el lliurament. |
| `application_id` | L'aplicació a la qual pertany l'esdeveniment. |
| `environment` | `live` o `sandbox`. |

## Estructures del payload

### `transaction.created` i `transaction.status.updated`

Tots dos esdeveniments comparteixen un únic payload: un resum compacte més el bloc [`action_required`](https://docs.didit.me/ca/transaction-monitoring/sdk-transaction-submission#accions-requerides-de-l’usuari) (o `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
}
```

El payload és intencionadament reduït: en rebre'l, obtén el registre complet amb [`GET /v3/transactions/{transaction_id}/`](https://docs.didit.me/ca/management-api/transactions/get). Retorna el detall complet (parts, `rule_runs`, `provider_results`, `travel_rule`, `cost_breakdown`, `decision_reason_code`), inclosa la *raó* del canvi d'estat.

### `transaction_rule_suggestions.created`

Aquest esdeveniment fa referència al teu conjunt de regles, no a una transacció concreta, així que no porta cap ID de transacció.
Mai no conté dades personals: només IDs de suggeriments, tipus, títols i recomptes.

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

| Camp | Descripció |
| - | - |
| `run_id` | L'execució de suggeriments que ha produït els nous suggeriments. |
| `open_count` | Quants suggeriments estan oberts per a l'aplicació després d'aquesta execució. |
| `suggestions[]` | Els suggeriments que ha creat aquesta execució, cadascun amb `suggestion_id`, `kind`, `title`, `rule_id` (null quan el suggeriment proposa una regla nova) i `expected_impact`. |

Els camps habituals de l'embolcall (`event_id`, `timestamp`, `application_id`, `environment`) s'afegeixen al cos.

### `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"
}
```

S'activa en cada transició, inclosa la transició a `FINISHED`, `CANCELLED` o `EXPIRED`. Quan una transferència arriba a un d'aquests estats, no s'envia cap més esdeveniment de la Travel Rule per a ella.
Un esdeveniment pot repetir l'estat de l'anterior, per exemple quan un reenviament torna a no trobar cap xarxa i la transferència es queda a `COUNTERPARTY_VASP_NOT_FOUND`.
`rail` és `INTERNAL`, `TRP`, `GTR`, `TRUST`, `VERIFYVASP`, `SYGNA`, `EMAIL`, `TEST_VASP` (una transferència sandbox a la qual ha respost el Didit Test VASP, que també porta `test_vasp: true` i `test_vasp_scenario`), o `""` mentre no s'ha seleccionat cap xarxa (sempre `""` en els registres sunrise).

Quan es crea una transacció, en un registre sunrise, en una consulta `TRP` entrant o en el tram entrant de la xarxa `INTERNAL`, Didit envia primer `transaction.created` i després els esdeveniments `travel_rule.status.updated` de la transacció.
Una regla que canvia l'estat de la transacció durant aquest pas també pot enviar `transaction.status.updated` abans de `transaction.created`.
Un registre sunrise que s'adjunta a una transacció de dipòsit que ja has creat no envia cap `transaction.created`.
Cada webhook es lliura i es reintenta de manera independent, així que un esdeveniment reintentat pot arribar després d'un de posterior: fes que el teu gestor es basi en `transaction_id` i torna a llegir la transacció quan necessitis el seu estat actual.

## Exemples de trajectòries d'estat

### Aprovació simple

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

### Revisió i aprovació posterior

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

### Verificació 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 de la 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)
```

Els dos primers esdeveniments provenen de la mateixa sol·licitud, i `transaction.created` es posa primer a la cua; cada webhook es lliura de manera independent, així que no confiïs en l'ordre d'arribada.
Una transferència enviada per TRUST mai no rep un esdeveniment `COMPLETED`, perquè TRUST no retorna res que Didit pugui llegir: es queda a `AWAITING_COUNTERPARTY` fins al termini de confirmació i després acaba a `EXPIRED`. Amb GTR, VerifyVASP i Sygna Bridge, Didit llegeix la resposta de la contrapart des de la xarxa, i l'esdeveniment la segueix.

## Verificació de la signatura

Cada lliurament se signa amb HMAC-SHA256 mitjançant el secret compartit de la destinació i inclou tres capçaleres de signatura més una marca de temps:

| Capçalera | Esquema |
| - | - |
| `X-Signature-V2` | HMAC sobre el JSON canònic (claus ordenades, Unicode preservat): **recomanat**. |
| `X-Signature` | HMAC sobre el JSON canònic escapat en ASCII (heretat). |
| `X-Signature-Simple` | HMAC sobre `"{timestamp}:{session_id}:{status}:{webhook_type}"`: **obsolet**. Els payloads de transacció no porten `session_id`, així que signa `"{timestamp}::{status}:{webhook_type}"`: no identifica ni la transacció ni el seu import o la seva puntuació. Un payload de `travel_rule.status.updated` tampoc no porta `status`, així que signa `"{timestamp}:::travel_rule.status.updated"`. Verifica amb `X-Signature-V2` i consulta [l'avís d'obsolescència](https://docs.didit.me/ca/integration/webhooks.md#x-signature-simple-està-obsoleta). |
| `X-Timestamp` | Segons de l’època Unix: rebutja els lliuraments de fa més de 5 minuts. |

```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 la [referència de webhooks](https://docs.didit.me/ca/integration/webhooks.md#verificació-de-la-signatura) per al codi de verificació complet, inclosa la comprovació de vigència de la marca de temps.

## Reintents i garanties de lliurament

* Davant d'un `5xx`, `404`, temps d’espera o error de connexió, Didit reintenta fins a **2 vegades**: aproximadament 1 minut després del primer error i, després, aproximadament 4 minuts més.
* Els lliuraments reintentats reutilitzen el mateix `event_id`: fes la deduplicació amb aquest camp al teu gestor.
* Després del darrer error, el lliurament d'aquest esdeveniment s'atura i la teva organització rep una notificació; les destinacions mai no es desactiven en silenci.
* Respon amb un `2xx` de manera ràpida (posa a la cua i després processa): els ACK lents comporten el risc de temps d’espera i de lliuraments duplicats.

## Idempotència

Processa cada webhook exactament una vegada desant `event_id` al teu propi magatzem de deduplicació. Pot passar que el mateix esdeveniment es torni a lliurar si el teu endpoint confirma lentament o es desconnecta a mitja sol·licitud.

## Subscripció

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

## Següents passos

> [card] [Referència de webhooks](https://docs.didit.me/ca/integration/webhooks.md)
>
> Destinacions, signatura, reintents.

> [card] [Estats](https://docs.didit.me/ca/transaction-monitoring/statuses.md)
>
> Màquina d'estats completa.

> [card] [Guia d'integració](https://docs.didit.me/ca/transaction-monitoring/integration-guide.md)
>
> Gestió de webhooks en producció.


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