> ## 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.

# Вебхуки мониторинга транзакций: события и подпись

> Didit: вебхуки событий транзакций, триггеры, структура payload и проверка подписи HMAC. 1 000 транзакций в месяц включены, далее $0.001 за каждую.

Transaction Monitoring отправляет три webhook-события, которые вместе покрывают полный жизненный цикл транзакции, и необязательное четвёртое, которое сообщает о новых предложениях по правилам. Все они передаются через стандартную инфраструктуру webhook Didit с подписью HMAC-SHA256. Подробнее об адресах назначения, подписи и тестировании см. в [справочнике по webhook](https://docs.didit.me/ru/integration/webhooks.md).

## Каталог событий

| Событие | Когда отправляется |
| - | - |
| `transaction.created` | Создана новая транзакция и завершена первичная оценка. Также отправляется для входящих транзакций, которые Didit создаёт за вас: регистрация [Travel Rule sunrise](https://docs.didit.me/ru/transaction-monitoring/travel-rule.md#регистрация-депозита-задним-числом) после депозита, входящий запрос `TRP` или перевод по каналу `INTERNAL` от другого клиента Didit. |
| `transaction.status.updated` | Статус транзакции изменился в результате проверки аналитиком, решения, принятого в сессии исправления, или действия правила, когда Didit повторно оценивает ваши правила после перехода Travel Rule (например, после подтверждения владения кошельком). Изменение статуса, которое применяет ваш `timeout_outcome` при истечении срока перевода, это событие не отправляет; вместо него отреагируйте на `travel_rule.status.updated` со значением `EXPIRED`. |
| `travel_rule.status.updated` | Обмен [Travel Rule](https://docs.didit.me/ru/transaction-monitoring/travel-rule.md) перешёл в новое состояние по исходящим и входящим переводам (входящая часть по каналу `INTERNAL`, регистрации sunrise и входящие запросы `TRP`), включая переход в `NOT_APPLICABLE` ниже порога. Для транзакции, которую создаёт Didit, эти события отправляются после её `transaction.created`. |
| `transaction_rule_suggestions.created` | В результате запуска формирования предложений по правилам были предложены новые изменения правил для вашего приложения. Отправляется только если вы включили **Webhook** в настройках уведомлений о предложениях по правилам, и только для запусков, которые создали предложения. |

События уровня сущностей (`user.status.updated`, `business.status.updated`) также могут возникать как следствие активности транзакций: см. [webhook сущностей](https://docs.didit.me/ru/entities/webhooks.md).

> [!NOTE]
>
> Приложения в песочнице не отправляют webhook `transaction.created` или `transaction.status.updated`. Чтобы протестировать эти события, используйте приложение в продакшн-среде или **API & Webhooks → Try Webhook** в консоли. Обмены Travel Rule в песочнице отправляют webhook `travel_rule.status.updated` с `environment: sandbox`; чтобы проверить обмен перед запуском в продакшн, используйте [Didit Test VASP](https://docs.didit.me/ru/transaction-monitoring/travel-rule-sandbox.md).

## Структура сообщения

Webhook Didit используют **плоское** тело: поля события находятся на верхнем уровне рядом с полями конверта, вложенного объекта `data` нет.
Каждая доставка содержит:

| Поле | Описание |
| - | - |
| `webhook_type` | Название события, например `transaction.created`. |
| `event_id` | Стабильный идентификатор события. При повторных попытках он остаётся тем же, `event_id` используйте для дедупликации. |
| `timestamp` / `created_at` | Unix-время в секундах, когда доставка была отправлена. |
| `application_id` | Приложение, которому принадлежит событие. |
| `environment` | `live` или `sandbox`. |

## Структура payload

### `transaction.created` и `transaction.status.updated`

Оба события используют одну структуру payload: краткую сводку и блок [`action_required`](https://docs.didit.me/ru/transaction-monitoring/sdk-transaction-submission#требуемые-действия-пользователя) (или `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
}
```

Payload намеренно краткий: получив его, запросите полную запись через [`GET /v3/transactions/{transaction_id}/`](https://docs.didit.me/ru/management-api/transactions/get). Она содержит полные данные (стороны, `rule_runs`, `provider_results`, `travel_rule`, `cost_breakdown`, `decision_reason_code`), включая *причину* изменения статуса.

### `transaction_rule_suggestions.created`

Это событие касается вашего набора правил, а не одной транзакции, поэтому идентификатора транзакции в нём нет.
Оно никогда не содержит персональных данных: только идентификаторы предложений, их типы, названия и количество.

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

| Поле | Описание |
| - | - |
| `run_id` | Запуск предложений, который создал новые предложения. |
| `open_count` | Сколько предложений открыто для приложения после этого запуска. |
| `suggestions[]` | Предложения, созданные этим запуском. Каждое содержит `suggestion_id`, `kind`, `title`, `rule_id` (null, если предложение предлагает новое правило) и `expected_impact`. |

Стандартные поля конверта (`event_id`, `timestamp`, `application_id`, `environment`) добавляются в тело.

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

Отправляется при каждом переходе, включая переход в `FINISHED`, `CANCELLED` или `EXPIRED`. Когда перевод достигает одного из этих статусов, для него больше не отправляются события Travel Rule.
Событие может повторять предыдущий статус, например когда повторная отправка снова не находит канал и перевод остаётся в `COUNTERPARTY_VASP_NOT_FOUND`.
`rail` принимает значения `INTERNAL`, `TRP`, `GTR`, `TRUST`, `VERIFYVASP`, `SYGNA`, `EMAIL`, `TEST_VASP` (перевод в песочнице, на который ответил Didit Test VASP; он также содержит `test_vasp: true` и `test_vasp_scenario`) или `""`, пока канал не выбран (для регистраций sunrise всегда `""`).

При создании транзакции, регистрации sunrise, входящем запросе `TRP` или входящей части по каналу `INTERNAL` Didit сначала отправляет `transaction.created`, а затем события `travel_rule.status.updated` этой транзакции.
Правило, которое меняет статус транзакции на этом шаге, может также отправить `transaction.status.updated` до `transaction.created`.
Регистрация sunrise, привязанная к уже созданной вами транзакции депозита, `transaction.created` не отправляет.
Каждый webhook доставляется отдельно, и для него отдельно выполняются повторные попытки, поэтому повторно отправленное событие может прийти после более позднего. Привязывайте обработчик к `transaction_id` и при необходимости повторно запрашивайте транзакцию, чтобы получить её текущее состояние.

## Примеры сценариев статусов

### Простое одобрение

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

### Проверка с последующим одобрением

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

### Дополнительная верификация

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

### Перевод по 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)
```

Первые два события приходят из одного запроса, и `transaction.created` ставится в очередь первым. Каждый webhook доставляется отдельно, поэтому не полагайтесь на порядок прихода.
Перевод, отправленный по TRUST, никогда не получает событие `COMPLETED`, потому что TRUST не возвращает Didit ничего, что тот может прочитать: перевод остаётся в `AWAITING_COUNTERPARTY` до срока подтверждения, а затем переходит в `EXPIRED`. По GTR, VerifyVASP и Sygna Bridge Didit читает ответ контрагента из сети, и событие следует за этим ответом.

## Проверка подписи

Каждая доставка подписывается по HMAC-SHA256 с использованием общего секрета адреса назначения и содержит три заголовка подписи и отметку времени:

| Заголовок | Схема |
| - | - |
| `X-Signature-V2` | HMAC по каноническому JSON (ключи отсортированы, Unicode сохраняется): **рекомендуется**. |
| `X-Signature` | HMAC по каноническому JSON с ASCII-экранированием (устаревший вариант). |
| `X-Signature-Simple` | HMAC по строке `"{timestamp}:{session_id}:{status}:{webhook_type}"`: **устарел**. Payload транзакций не содержит `session_id`, поэтому подписывается строка `"{timestamp}::{status}:{webhook_type}"`. Она не идентифицирует ни транзакцию, ни её сумму или оценку. Payload `travel_rule.status.updated` также не содержит `status`, поэтому подписывается строка `"{timestamp}:::travel_rule.status.updated"`. Проверяйте подпись по `X-Signature-V2` и ознакомьтесь с [уведомлением об устаревании](https://docs.didit.me/ru/integration/webhooks.md#x-signature-simple-устарел). |
| `X-Timestamp` | Секунды эпохи Unix: отклоняйте доставки старше 5 минут. |

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

Полный код проверки, включая проверку актуальности отметки времени, см. в [справочнике по webhook](https://docs.didit.me/ru/integration/webhooks.md#проверка-подписи).

## Повторные попытки и гарантии доставки

* При ответе `5xx`, `404`, таймауте или сбое соединения Didit повторяет доставку до **2 раз**: примерно через 1 минуту после первой ошибки, затем ещё примерно через 4 минуты.
* Повторные доставки используют тот же `event_id`. Устраняйте дубликаты по этому полю в вашем обработчике.
* После последней неудачной попытки доставка этого события прекращается, и ваша организация получает уведомление. Адреса назначения никогда не отключаются без уведомления.
* Быстро возвращайте ответ `2xx`: сначала поставьте событие в очередь, затем обработайте его. Медленные подтверждения (ACK) создают риск таймаутов и повторных доставок.

## Идемпотентность

Обрабатывайте каждый webhook ровно один раз, сохраняя `event_id` в собственном хранилище для устранения дубликатов. Повторная доставка того же события возможна, если ваш endpoint отвечает медленно или разрывает соединение во время запроса.

## Подписка

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

## Дальнейшие шаги

> [card] [Справочник по webhook](https://docs.didit.me/ru/integration/webhooks.md)
>
> Адреса назначения, подписи, повторные попытки.

> [card] [Статусы](https://docs.didit.me/ru/transaction-monitoring/statuses.md)
>
> Полная модель состояний.

> [card] [Руководство по интеграции](https://docs.didit.me/ru/transaction-monitoring/integration-guide.md)
>
> Обработка webhook в продакшне.


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