Перейти к основному содержанию
Didit проводит обмен данными отправителя и получателя в соответствии с требованиями FATF и Регламентом ЕС о переводе средств (Transfer of Funds Regulation, TFR) для переводов виртуальных активов. Платформа находит канал, по которому можно связаться с VASP контрагента, согласовывает по нему необходимые данные, отслеживает обмен через жизненный цикл из 13 состояний и сообщает, когда обмен завершён, чтобы вы могли разблокировать средства ончейн.

Обзор

Правило Travel Rule FATF (Рекомендация 16), реализованное в ЕС в виде Регламента о переводах средств (TFR), требует, чтобы отправляющий поставщик услуг виртуальных активов (VASP-отправитель) собрал и передал информацию об отправителе и получателе VASP-получателю (VASP-получатель) до того, как перевод виртуальных активов будет завершён. В отличие от большинства юрисдикций, TFR ЕС действует без минимальной суммы: в сферу действия попадает каждый криптоперевод, а не только переводы выше порога отчётности. Продукт Didit для Travel Rule представляет собой управляемый обмен, а не просто поле с данными:
  • Вы отправляете перевод с transaction_category: "travelRule".
  • Didit перебирает каналы в порядке приоритета, чтобы связаться с поставщиком-контрагентом, проводит обмен через первый канал, по которому удаётся связаться с ним, и, если другая сторона возвращает имя получателя, сверяет его с тем, которое отправили вы.
  • Только после того, как обмен перейдёт в статус COMPLETED, следует транслировать перевод ончейн.
  • Затем вы передаёте в Didit хеш блокчейн-транзакции, чтобы закрыть обмен со статусом FINISHED.
В этом релизе доступны семь каналов, которые перебираются в строгом порядке приоритета: побеждает первый, который может связаться с контрагентом. Сетевым каналам (GTR, TRUST, VERIFYVASP, SYGNA) нужно членство в сети со стороны отправителя, и сейчас это членство должно быть вашим собственным. Подключите учётные данные членства в консоли Marketplace (категория Travel Rule networks), и ваши переводы будут проходить под вашей собственной сетевой идентичностью. Didit сейчас не имеет платформенного членства ни в одной из четырёх сетей и проходит процедуру подключения в качестве провайдера. Пока эта процедура не завершена, platform_available равно false для всех четырёх сетей, режим didit в настройках (маршрутизация через платформенное членство Didit) недоступен ни для одной сети, а в disabled_networks нечего отключать. Членство, которое вы подключаете самостоятельно, всегда имеет приоритет над платформенным членством. Ни одно сетевое членство не включено по умолчанию, в том числе GTR. Включение Travel Rule не активирует GTR, TRUST, VerifyVASP или Sygna. Чтобы узнать действующие режимы networks для вашего приложения, см. Get Travel Rule Settings, а что каждая сеть охватывает сегодня, см. Travel Rule networks. Если все четыре сообщают unavailable, ни одна из них не может провести обмен: подключите собственное членство в Marketplace или используйте доступного контрагента INTERNAL, TRP или EMAIL. При сохранении членства его код участника должен быть вашим собственным: сохранение возвращает 400, если код совпадает с кодом Didit в этой сети или с кодом, который уже закреплён за членством другого приложения. Для членства в GTR нужен приватный ключ ed25519 (base64), тот, чей публичный ключ зарегистрирован в портале GTR: он шифрует персональные данные, которые вы отправляете, и расшифровывает данные, которые вы получаете. Членство в VerifyVASP указывает на ваш собственный VerifyVASP Enclave: его URL принадлежит вашему Enclave, а token: это тот, который ваш Enclave предъявляет Didit. Даже при собственном членстве канал сети маршрутизирует перевод только тогда, когда в его beneficiary_data указан идентификатор контрагента в этой сети (counterparty_gtr_vasp_code, counterparty_trust_membership_id, counterparty_verifyvasp_id или counterparty_sygna_vasp_code), либо когда в записи каталога контрагента этот идентификатор указан для данной сети (см. Routes). platform_available показывает, настроено ли платформенное членство, а не результат живой проверки работоспособности сети. Аналогично, connected означает, что учётные данные вашего членства сохранены и прошли локальную проверку; первый живой обмен подтверждает их в сети. Режим didit или own не гарантирует, что контрагент будет доступен или что доставка пройдёт успешно. С вашим собственным членством Didit получает ответ контрагента по GTR, VERIFYVASP и SYGNA из ответа сети или опрашивая сеть каждые 5 минут, и по нему завершает или отклоняет перевод. TRUST только отправляет: перевод, отправленный по этому каналу, остается в AWAITING_COUNTERPARTY, пока не истечет confirmation_timeout_hours, после чего переходит в EXPIRED, и применяется ваш timeout_outcome. Получение перевода, который другой VASP отправляет вам через сеть, пока не включено, а ни один обмен через сеть еще не был завершен в рабочей сети: см. Сети Travel Rule. Остальные сети: CODE (корейский альянс), TRISA, Notabene и Veriscope перечислены в Marketplace в консоли в виде плиток для запроса доступа. Интеграции с ними у Didit пока нет, поэтому сегодня через них нельзя направить ни один перевод. Если ни один из семи каналов не может определить контрагента, Didit выполняет еще один шаг определения, прежде чем сдаться: атрибуцию через блокчейн-аналитику. Адрес кошелька назначения сверяется с данными блокчейн-аналитики; если кошелек принадлежит известной бирже или кастодиану, Didit записывает атрибутированный VASP в перевод, добавляет его в справочник и один раз повторно запускает каналы с новым названием. Подробнее о поведении и ценах см. Атрибуция через блокчейн-аналитику. Перевод, адрес назначения которого объявлен как некастодиальный кошелек, пропускает все семь каналов, так как VASP-контрагента для обмена нет, и сразу переходит к подтверждению владения кошельком конечным пользователем.
Didit также управляет стороной обмена, обращенной к контрагентам: публичными эндпоинтами TRP для идентификации и запросов, которые другие VASP вызывают, чтобы связаться с вами, эндпоинтами обратного вызова и процессом получения по электронной почте. Вы никогда не вызываете их сами, это управляемая инфраструктура. Однако на странице Совместимость Travel Rule описан каждый из них, чтобы VASP-контрагенты могли подключиться к вам. Вы взаимодействуете с Travel Rule только через эндпоинты /v3/, описанные на этой странице.

Что выполняется при отправке перевода

Проверки выполняются в рамках одного вызова POST /v3/transactions/. К моменту получения ответа скрининг уже выполнен, перевод сохранён, а правила оценены. Обмен начинается уже после ответа: Didit выбирает канал и связывается с контрагентом вне вашего запроса, поэтому ни одна сеть и ни один контрагент не могут замедлить создание или привести к его ошибке. Каждое последующее изменение статуса приходит в виде webhook travel_rule.status.updated, в котором указан канал. Объект перевода, который возвращают endpoint’ы Travel Rule, содержит поле delivery, показывающее, дошло ли сообщение. Опрашивать ничего не нужно. Асинхронными остаются обмен, ответ контрагента и действия конечного пользователя. Пошагово:
  1. Валидация и привязка субъекта. Payload проверяется. Если transaction_id уже использовался в вашем приложении, запрос отклоняется с кодом 400. Если два запроса с одинаковым идентификатором приходят одновременно, один из них получает 409. subject связывается с вашим профилем пользователя или компании через vendor_data, чтобы перевод отображался в этом профиле для мониторинга. Если vendor_data субъекта или контрагента находится в списке блокировки, запрос отклоняется с кодом 400, и транзакция не создаётся. Кошельки, банковские счета, IP-адреса и устройства из списка блокировки проверяются на шаге 6, где транзакция отклоняется уже после маршрутизации.
  2. Обогащение данными об устройстве и сети. IP-адреса участников обогащаются геоданными. Контекст устройства (он передаётся автоматически при отправке через SDK) нормализуется и попадает в граф устройств. Это тот же граф, который наполняют сессии KYC, поэтому связь «по устройству» работает и при онбординге, и для транзакций.
  3. Криптоскрининг. Для криптопереводов с включённым мониторингом соответствующий кошелёк (или ончейн-хеш) проверяется в аналитике блокчейна до того, как начнётся какой-либо обмен: риск санкций, близость к миксерам, украденные средства. См. Криптоскрининг и скрининг кошельков.
  4. Перевод Travel Rule. Движок обмена проверяет порог и учитывает декларацию о некастодиальном кошельке, после чего сохраняет перевод. Статус будет NOT_APPLICABLE, если сумма ниже вашего порога; UNCONFIRMED_OWNERSHIP для заявленного некастодиального кошелька; COMPLETED, если второй кошелёк принадлежит вам и проверен. Во всех остальных случаях AWAITING_COUNTERPARTY, а канал на этом этапе ещё не выбран. Каждое изменение статуса ставит в очередь событие travel_rule.status.updated. Didit отправляет его после transaction.created (шаг 7).
  5. Выпуск сессии подтверждения владения кошельком. Если переводу нужно подтверждение контроля над кошельком со стороны конечного пользователя и включён параметр auto_wallet_verification, создаётся сессия виджета подтверждения владения кошельком, которая возвращается в action_required.
  6. Оценка правил. Выполняются все активные правила. Пакет правил EU TFR реагирует на статус перевода, заданный на шаге 4. Ваши пользовательские правила видят результаты скрининга шага 3. Правило может удержать транзакцию (IN_REVIEW), отклонить её или потребовать дополнительной верификации (AWAITING_USER).
  7. Биллинг и уведомления. За исходящую транзакцию взимается плата по одной строке: линия Travel Rule, если она запустила обмен, и скрининг транзакций в остальных случаях. С входящего перевода плата не взимается, а за приложение в песочнице плата никогда не взимается. Ответ возвращается, после чего transaction.created отправляется на ваши адреса назначения webhook, а затем следуют события travel_rule.status.updated шага 4.
  8. Маршрутизация, после ответа. Didit последовательно перебирает семь каналов в порядке приоритета. Если ни один из них не разрешает маршрут, атрибуция контрагента по аналитике блокчейна определяет контрагента, и каналы проверяются повторно один раз. Первый доступный канал ведёт обмен: перевод остаётся в AWAITING_COUNTERPARTY на этом канале, пока контрагент отвечает. Если найден контрагент INTERNAL, перевод сразу завершается (COMPLETED или COUNTERPARTY_MISMATCHED_DATA). Если ни один канал не достигает контрагента, статус становится COUNTERPARTY_VASP_NOT_FOUND. Каждое изменение запускает travel_rule.status.updated, а правила вычисляются заново при каждом изменении статуса или канала. Для перевода, который маршрутизация оставила в COUNTERPARTY_VASP_NOT_FOUND, сессия виджета подтверждения владения кошельком создаётся в этот момент (если включён auto_wallet_verification). Получить её можно из action_required транзакции с помощью Получить транзакцию.

Как каждый этап соответствует вашим обязательствам

Примечание о тестировании. Приложения в песочнице не выполняют оценку правил и биллинг и не отправляют webhook transaction.created или transaction.status.updated. Транзакции в песочнице остаются в статусе APPROVED, если только при истечении перевода Travel Rule не применяется ваш timeout_outcome. Оплата в песочнице не взимается. Движок обмена Travel Rule работает в приложениях песочницы, если Travel Rule включён, и взаимодействует только с одним контрагентом. Каждый исходящий обмен направляется к Didit Test VASP по каналу TEST_VASP. Тестовый VASP отвечает внутри Didit по выбранному вами сценарию, поэтому перевод может завершиться, быть отклонён, завершиться несоответствием данных, стать недоступным, истечь или перейти в ожидание подтверждения владения кошельком. Сообщения, связанные с комплаенсом, никогда не доходят до реального VASP-контрагента, а webhook travel_rule.status.updated отправляется с environment: sandbox. Для входящих переводов: тестовый VASP может отправить вам перевод (Инициировать входящий перевод от тестового VASP). Регистрация (POST /v3/travel-rule/inbound/) сверяется с адресной книгой кошельков так же, как в боевом приложении: ON_HOLD, UNCONFIRMED_OWNERSHIP, COUNTERPARTY_MISMATCHED_DATA или COMPLETED, а затем FINISHED. См. Тестирование Travel Rule в песочнице.

Lifecycle

Каждый перевод имеет status из 13 значений. Обмен начинается со статуса AWAITING_COUNTERPARTY в обычном случае, со статуса NOT_APPLICABLE, если сумма ниже вашего threshold_amount или в запросе на платёж Lightning не указан получатель (см. Lightning), либо со статуса UNCONFIRMED_OWNERSHIP, если адрес назначения объявлен некастодиальным кошельком. Завершается он одним из конечных статусов: FINISHED, CANCELLED или EXPIRED. У перевода собственный конечный автомат, отдельный от статуса проверки базовой транзакции. Статус обмена сам по себе никогда не меняет статус транзакции, за одним исключением: при истечении срока применяется ваш настроенный timeout_outcome (или политика sunrise или для неотвечающих контрагентов, если она действует). Вместо этого ваши правила пересчитываются при каждом переходе, поэтому правила EU TFR или любое правило, которое опирается на travel_rule_status, может изменить транзакцию, например перевести её в IN_REVIEW. Если переводу нужно подтверждение владения кошельком от конечного пользователя, в транзакцию добавляется блок action_required, но её status остаётся таким, каким его оставили ваши правила. Состояние обмена читайте из travel_rule.status, а решение «разрешить / удержать / отклонить» из собственного status транзакции. Каждый переход (и каждый статус, полученный пассивно) также дублируется в пользовательские значения транзакции, чтобы на них могли опираться правила: custom_values.travel_rule_status (канонический статус), custom_values.travel_rule_rail (канал, который провёл обмен; пусто, пока канал не выбран) и custom_values.travel_rule_regime (определяется по jurisdiction из ваших настроек: SG → singapore, GB → uk, а любой код члена ЕС или ЕЭЗ, например DE или FR, → eu; коды, которых нет в таблице, сохраняются в нижнем регистре, например US → us). Значение props.travel_rule_regime, которое вы передаёте сами, всегда имеет приоритет над вычисленным.
Никогда не транслируйте транзакцию в сеть до COMPLETED. Перевод средств в блокчейн-сеть, пока перевод находится в статусе AWAITING_COUNTERPARTY, UNCONFIRMED_OWNERSHIP или в любом состоянии расхождения или отказа, сводит на нет смысл обмена данными. Настройте шаг трансляции так, чтобы он выполнялся только при status == "COMPLETED", затем вызовите PATCH .../travel-rule/ с полученным payment_txn_id, чтобы перевести перевод в FINISHED.
Переходы между статусами в общем виде (CANCELLED дополнительно достижим из каждого нетерминального статуса с action: "cancel"):

Доставка и повторные попытки

Didit отправляет каждое сообщение, которое перевод должен отправить, вне запроса, который его вызвал, и повторяет отправку, пока другая сторона не примет его. Объект перевода, возвращаемый в ответе на Завершение, отмена или повторная отправка, Подтверждение владения и регистрация sunrise, содержит последнее такое сообщение в поле delivery, то же относится и к встроенному объекту travel_rule транзакции; оно равно null, если перевод никогда не требовал такого сообщения. При сбое, от которого сеть может еще оправиться (нет ответа в срок, сбой соединения, HTTP 408, 429 или 5xx), сообщение повторно отправляется через 30 секунд, 2 минуты, 10 минут, 30 минут и 1 час. Если первое сообщение окончательно не доставлено (сеть отклонила его или шестая попытка также завершилась неудачей), перевод переходит в COUNTERPARTY_VASP_NOT_REACHABLE, а last_error записывается в его timeline; вы можете повторить отправку с action: "resend". Любое другое сообщение, которое окончательно не доставлено, отмечается в timeline. Поле delivery никогда не заменяет status перевода: состояние обмена определяйте по status, а delivery используйте, чтобы понять, почему перевод все еще ожидает.

Устаревшие значения статусов

Если вы уже отправляли travel_rule_details.status до этого релиза, эти значения продолжают работать и автоматически сопоставляются. Написание статусов в camelCase (например, counterpartyVaspNotFound) также принимается, как при отправке, так и в значениях условий правил, и приводится к канонической форме:

Настройка

Настройте профиль VASP и политику согласования один раз для каждого приложения с помощью GET/PUT /v3/travel-rule/settings/.
PUT выполняет частичное обновление: передавайте только те поля, которые хотите изменить.

Строгость сопоставления имён

Режимы проверки владения

ownership_validation_mode определяет, какое подтверждение владения кошельком требуется, прежде чем перевод сможет использовать этот кошелёк. Кошелёк сохраняется за конечным пользователем после верификации. В режимах FULL и SIMPLIFIED последующий перевод использует его, только если такое подтверждение, допустимое в этом режиме для данного перевода, подтверждает владение кошельком. Если для inbound_auto_accept в адресной книге нет подходящего кошелька, владельца определяет криптокошелёк, который вы записали ровно в транзакциях одного конечного пользователя. Didit добавляет этот кошелёк в адресную книгу конечного пользователя как вашу самодекларацию, с пометкой «верифицирован», если режим не FULL. Didit никогда не добавляет кошелёк, который адресная книга уже содержит под другим именем той же сети (eth и ethereum), включая кошелёк, записанный как принадлежащий другому VASP. Проверки адресов, которые другие VASP выполняют перед отправкой (через GTR, Sygna и VerifyVASP), также следуют режиму: при FULL кошелёк, который конечный пользователь только задекларировал, не сообщается как принадлежащий вам.

Передача данных

data_sharing_fields определяет, какие поля Didit передаёт контрагенту о ваших пользователях, отдельно для физических лиц и компаний:
  • Физические лица могут передавать name, wallet_address, address, national_id, customer_identification, date_of_birth и place_of_birth. Компании передают первые пять.
  • name и wallet_address передаются всегда.
  • Тип субъекта, который вы никогда не выбирали, передаёт все поля. Пустой список тоже является выбором: он передаёт только имя и кошелёк.
  • PUT объединяет настройки по типу субъекта, поэтому отправка individual не меняет сохранённый выбор для company.
  • Выбор, который не может выполнить обязательства отправителя по вашей jurisdiction (режим EU TFR или FATF Recommendation 16), включая пустой список, отклоняется с 400. Ошибки приводятся в data_sharing_fields по типу субъекта.

Сопоставление по полям

matching_rules задаёт, насколько строго сравнивается каждое поле данных контрагента с тем, что у вас есть:
Каждое значение: NONE, STRICT, CONTAINS, DEFAULT или FUZZY. По умолчанию: name следует name_matching_strictness, date_of_birth и address имеют значение NONE, а account имеет значение STRICT. Пропущенное поле сохраняет значение по умолчанию, а поле, которое для данного типа субъекта не сравнивается (у компании нет date_of_birth), отклоняется с 400.

Пороги по юрисдикциям

threshold_amount существует потому, что сфера действия Travel Rule различается у разных регуляторов. Didit по умолчанию использует 0.00, позицию ЕС, поэтому по каждому переводу происходит обмен данными, пока вы сознательно не повысите порог: Укажите в threshold_amount значение, установленное вашим регулятором; переводы ниже него получают статус NOT_APPLICABLE без передачи каких-либо данных идентификации. Пороги меняются, поэтому уточняйте актуальное значение у своего юрисконсульта по комплаенсу, а не считайте эту таблицу юридической консультацией.

Модель данных IVMS 101

Отраслевым стандартом для данных, которыми обмениваются в рамках Travel Rule, является IVMS 101 (interVASP Messaging Standard). travel_rule_details.originator_data и travel_rule_details.beneficiary_data представляют собой данные о лице в формате, похожем на IVMS 101. Didit упаковывает их в родной конверт каждого канала: канал TRP передаёт блоки IVMS101 (ivms101.2023, версия указывается в поле ivms_version перевода), канал Sygna использует собственный вариант IVMS, зашифрованный по ECIES, а каналы GTR, TRUST и VERIFYVASP используют собственные зашифрованные конверты. Благодаря этому одна и та же структура данных доходит до каждого контрагента в формате, который ожидает его протокол. Didit считывает следующие ключи из каждого блока данных лица при формировании сообщения IVMS 101: first_name и last_name (или один ключ name или full_name), date_of_birth, place_of_birth, residence_country (или country), vendor_data (или customer_identification) и wallet_address. Два практических правила:
  • Латиница. IVMS 101 требует значения в латинице, поэтому транслитерируйте имена, написанные с использованием другой системы письма. Сопоставление имён в Didit сравнивает нормализованные наборы токенов, поэтому различия в порядке слов и регистре не приводят к сбою обмена, а разные системы письма на двух сторонах приводят к сбою обмена.
  • Отправляйте проверенные данные. Если субъект, к которому относится vendor_data, является пользователем с одобренной верификацией KYC в Didit, Didit передаёт проверенную личность, а не значения, которые вы ввели: см. Проверенные данные и происхождение. Для любого другого субъекта Didit передаёт именно те originator_data, которые вы отправили, поэтому формируйте их из личности, которую вы проверили при онбординге, а не из непроверенного поля формы. Именно этого требует формулировка FATF «необходимая и точная информация об отправителе». На канале INTERNAL приложение получателя также получает subject, который вы отправили (имя, vendor_data, дату рождения, страну и адрес), как контрагента его входящей транзакции.

Проверенные данные и происхождение

Когда субъектом перевода является один из ваших пользователей, физическое лицо с одобренной верификацией KYC, Didit формирует эту сторону обмена из проверенных данных:
  • отправитель исходящего перевода, который вы создаёте через POST /v3/transactions/;
  • получатель входящего перевода, о котором вы сообщаете через POST /v3/transactions/, или через POST /v3/travel-rule/inbound/, если holder_vendor_data сопоставленного кошелька указывает на этого пользователя.
В POST /v3/transactions/ оба варианта применяются к транзакции, у которой transaction_category равен travel_rule, пока Travel Rule включён. Проверенные имена, дата и место рождения, номер и тип документа, адрес и страна проживания (по этому адресу) имеют приоритет над значениями, которые вы отправили. Поле, которого нет в результатах верификации, сохраняет ваше значение. То же относится к полному адресу, который вы отправили, если проверенный адрес недостаточно полон для передачи. В metadata перевода, в объекте travel_rule внутри транзакции, фиксируется, откуда взято каждое поле и где ваши значения отличались:
*_provenance сопоставляет каждое поле со значением verified или caller. *_verified_differences перечисляет все поля, где вы отправили значение, отличающееся от проверенного, без учёта регистра, пробелов и двух- или трёхбуквенных кодов стран. Пока данные входящего перевода замаскированы, различия не выводятся. Didit вычисляет оба этих ключа сам, поэтому их копии, а также копии name_match, которые вы передаёте в travel_rule_details.metadata, отбрасываются.

Отправка IVMS 101 напрямую

Помимо плоских объектов originator_data и beneficiary_data, travel_rule_details (в POST /v3/transactions/) и POST /v3/travel-rule/inbound/ принимают настоящий IVMS 101:
  • блок originator и блок beneficiary, где originatorPersons или beneficiaryPersons содержат naturalPerson или legalPerson, а также accountNumber;
  • те же блоки внутри объекта ivms101 (или IVMS101);
  • одно лицо IVMS 101 или целый блок как originator_data или beneficiary_data.
С каждой стороны передаётся одно лицо: naturalPerson или legalPerson, и в последнем случае сторона является компанией. Непустой плоский originator_data или beneficiary_data имеет приоритет над блоком для той же стороны.
Некорректный блок возвращает 400 с ключом, соответствующим его пути, например: {"travel_rule_details": {"originator.originatorPersons": ["must be a non-empty list of originator persons."]}}. В POST /v3/travel-rule/inbound/ те же ключи находятся на верхнем уровне, а если вы использовали обёртку, то внутри ivms101..

Обязательные данные по режимам

Пока Travel Rule включён, исходящий перевод, сумма которого равна или превышает ваш порог, должен содержать данные, которые требует его режим, иначе возвращается 400. Проверяются только те стороны, которые вы отправили, проверенные данные учитываются для отправителя, а ошибки относятся к travel_rule_details.originator_data или travel_rule_details.beneficiary_data, например: "EU TFR requires the originator name." или "FATF Recommendation 16 requires the beneficiary wallet address.". Для отправителя проверка читает поля, которыми ваше приложение делится в data_sharing_fields, поскольку только они передаются по каналу; получатель проверяется так, как вы его отправили. Если перевод соответствует режиму только с помощью поля, которым вы не делитесь, в ответе 400 названо требование режима, а затем поля, которыми делятся ваши настройки. Официальный номер документа - это national_id (или document_number), идентификационный номер клиента - это customer_identification (или vendor_data), а дата рождения - это date_of_birth. Пустой beneficiary_data.wallet_address заполняется из counterparty.payment_method.account_id до проверки. Вывод UTXO, выход получателя которого Didit определил по travel_rule_details.utxo, считается содержащим адрес. Перевод Lightning, к которому Travel Rule не применяется, не проверяется.

Travel Addresses

Travel Address - это компактная закодированная строка, которая указывает на endpoint входящих запросов TRP VASP-получателя. Она никогда не раскрывает исходный URL или адрес кошелька. После включения Travel Rule Didit формирует ваш собственный travel_address (он доступен только для чтения в настройках), чтобы вы могли передать его другим VASP, которые связываются с вами по каналу TRP. Каждый ваш кошелёк в адресной книге также получает собственный receiving_travel_address: запрос, отправленный на него, указывает этот кошелёк, поэтому Didit не нужно искать его по номеру счёта или имени владельца. Публичный хост TRP в Didit пока не отвечает другим VASP, поэтому приём по TRP станет доступен только после этого: см. Сети Travel Rule. Если вам известен Travel Address контрагента, сохраните его в записи адресной книги кошелька назначения, чтобы Didit направлял переводы этому получателю напрямую по TRP, а не переходил к отправке по электронной почте. Didit проверяет Travel Address, которые вы сохраняете в записи адресной книги кошелька или передаёте в originating_vasp: каждый должен начинаться с ta и декодироваться в URL запроса с параметром t=i, поэтому заглушка вида ta... возвращает 400. beneficiary_data.travel_address в транзакции не проверяется: если он не декодируется, канал TRP пропускается. Кодирование выполняется в формате Base58Check с префиксом ta: хост и путь endpoint (который всегда содержит пару ключ–значение t=i) плюс четырёхбайтовая контрольная сумма double-SHA-256, закодированная в base58. Поэтому настоящий адрес выглядит так и имеет длину около 100 символов:
В тех случаях, когда Didit проверяет Travel Address, значение, которое не является корректно оформленным адресом с верной контрольной суммой, возвращает 400 с "Invalid travel address.". Берите настоящий адрес из ответа с вашими настройками или от самого контрагента.

Тестирование без сетевых учётных данных

Для тестирования сценариев, не требующих членства, не нужно состоять в GTR, TRUST, VerifyVASP или Sygna. Но для тестируемого приложения по-прежнему нужен API-ключ Didit.
  • Исходящие в песочнице: отправьте транзакцию travelRule из приложения песочницы. После проверок порога и некастодиального кошелька Didit Test VASP отвечает на неё внутри Didit по сценарию, который вы выберете по кошельку или имени получателя: принятие, отклонение, несовпадение имени, недоступен, таймаут или некастодиальный. Ни сеть, ни электронная почта, ни другой клиент Didit ничего не получают.
  • Входящие в песочнице: попросите тестовый VASP отправить вам перевод с помощью Trigger Test VASP Inbound Transfer или зарегистрируйте его сами через Register Inbound Travel Rule Transfer. Проверьте неизвестный кошелёк, непроверенный кошелёк, несовпадение имени получателя и проверенный совпадающий кошелёк. Эти сценарии воспроизводят реальные решения по входящим переводам без обращения к сети.
  • Два контролируемых продакшн-приложения: используйте канал INTERNAL между двумя вашими продакшн-приложениями (принимающее приложение доступно для поиска и подтверждает контроль над кошельком назначения подписанным сообщением или тестовым депозитом) либо контролируемого контрагента TRP, чтобы протестировать обмен между двумя сторонами без коммерческих сетевых учётных данных. Продакшн-приложения могут приводить к обычным списаниям и уведомлениям, поэтому используйте тестовые данные о личности и контрагентов, которых вы контролируете.
Все сценарии и их точные результаты описаны в Тестирование Travel Rule в песочнице. Успешные тесты в песочнице или локальном протоколе не подтверждают учётные данные, членство или совместимость коммерческой сети в продакшене. Для этого нужна авторизованная песочница сети или контролируемый обмен в продакшн-среде с действительными учётными данными членства.

Отправка перевода

Включите перевод в POST /v3/transactions/, установив transaction_category в значение travelRule (или travel_rule) и прикрепив объект travel_rule_details (псевдоним: travelRule). Блок travel_rule_details необязателен, но если вы его прикрепили, поле travel_rule_details.status обязательно: блок без него возвращает 400. Если Travel Rule включено для вашего приложения, Didit игнорирует переданное вами значение status и сам управляет обменом:
  1. Если сумма ниже настроенного вами threshold_amount, перевод сразу переходит в NOT_APPLICABLE, и никакой обмен не выполняется.
  2. Если платёжный метод контрагента указывает, что адрес назначения принадлежит некастодиальному кошельку (payment_method.method_type: "unhosted_wallet"), каналы не пробуются: перевод сразу переходит в UNCONFIRMED_OWNERSHIP, и виджет подтверждения владения кошельком создаётся автоматически (см. Объявленные некастодиальные кошельки).
  3. В остальных случаях каналы запускаются в порядке приоритета, и первый канал, который может связаться с контрагентом, ведёт обмен:
    • INTERNAL – ровно один другой клиент Didit имеет подписанное сообщение или тестовый депозит как доказательство владения beneficiary_data.wallet_address, созданное после последней смены владельца кошелька. Ваше собственное приложение никогда не учитывается, а если ваш конечный пользователь выбрал участника сети Didit в виджете подтверждения владения кошельком, учитывается только доказательство этого участника. Приложение этого клиента работает в продакшн-среде (не в песочнице), имеет включённое Travel Rule и установленный is_discoverable, а также является тем VASP, которого вы указали при передаче counterparty_vasp_lei или counterparty_vasp_name. Кошелёк, который только заявлен самостоятельно, подтверждён скриншотом, не верифицирован или лишь указан в адресной книге, никогда не получает ничего через INTERNAL.
    • TRP – для актива перевода в сети перевода есть сопоставление Digital Token Identifier (см. Идентификаторы активов TRP), и в payload передан beneficiary_data.travel_address, либо в записи адресной книги получателя сохранён такой адрес, либо контрагент находится в справочнике (по counterparty_vasp_lei или counterparty_vasp_name) и имеет endpoint TRP.
    • GTR, TRUST, VERIFYVASP, SYGNA – вы подключили собственное членство в этой сети в разделе Marketplace, и контрагент определяется по идентификатору сети (явное поле или запись в справочнике, см. Поля определения контрагента). У Didit пока нет членства в платформе в этих сетях (компания проходит подключение как провайдер), поэтому disabled_networks на сегодня не меняет этот шаг.
    • EMAIL – передан beneficiary_data.counterparty_vasp_email, или counterparty_vasp_name соответствует записи в справочнике с контактной электронной почтой для комплаенса.
  4. Если ни один канал не может определить контрагента, Didit запускает цепочку поиска контрагента. Если включён vasp_attribution_enabled, цепочка включает атрибуцию через блокчейн-аналитику: адрес назначения кошелька сопоставляется с VASP, которому принадлежит кошелёк, атрибутированное имя записывается в beneficiary_data.counterparty_vasp_name (если поле пустое) и в справочник, после чего каналы запускаются повторно один раз. Поэтому кошелёк, атрибутированный VASP, у записи которого в справочнике есть endpoint TRP, идентификатор сети или контактная электронная почта для комплаенса, маршрутизируется уже на этом втором проходе.
  5. Если контрагент всё ещё не определён, перевод переходит в COUNTERPARTY_VASP_NOT_FOUND. Для исходящих переводов Didit автоматически создаёт виджет подтверждения владения кошельком (когда включён auto_wallet_verification), чтобы перевод всё же мог завершиться как перевод на некастодиальный кошелёк. Позже, когда будут более точные данные о контрагенте, вы можете повторно запустить маршрутизацию с action: "resend".
Шаги 1 и 2 выполняются внутри запроса. Шаги 3–5 запускаются сразу после ответа: перевод возвращается со статусом AWAITING_COUNTERPARTY и protocol: null, а каждый результат приходит в виде webhook travel_rule.status.updated.

Идентификаторы активов TRP

TRP обозначает передаваемый актив с помощью его ISO 24165 Digital Token Identifier (DTI). DTI идентифицирует токен в одном блокчейне, поэтому у токена, существующего в нескольких сетях, есть отдельный DTI для каждой сети. Didit отправляет переводы через TRP для следующих активов: Идентификаторы USDC – это те, которые эмитент токена публикует в своей белой книге по MiCA. Для токена, существующего в нескольких сетях, перевод должен указать сеть в transaction_details.crypto_params.crypto_chain (например, CELO, BASE, OP, ARB или POL). Без сети или в сети без сопоставления перевод не отправляется через TRP, и Didit пробует оставшиеся каналы: сначала сети интероперабельности, затем электронную почту. USDT пока не сопоставлен. Токену без сопоставления DTI, например собственному стейблкоину компании, нужно, чтобы его эмитент зарегистрировал DTI в регистрационном органе ISO 24165: по одному DTI на каждый токен в каждом блокчейне. Когда DTI будут получены, свяжитесь со службой поддержки Didit и укажите каждый DTI, его блокчейн и количество десятичных знаков токена, после чего Didit добавит сопоставление. Входящие запросы TRP, которые содержат сопоставленный DTI для конкретной сети, записываются с валютой токена, суммой в десятичных знаках токена и сетью.

Поиск контрагента

Перевод, который содержит только адрес кошелька, всё равно доходит до контрагента, если его можно идентифицировать. Прежде чем исходящий перевод будет признан неподдающимся маршрутизации, Didit по порядку проверяет следующие шаги и останавливается на первом, который называет VASP с маршрутом: Поле discovery перевода перечисляет каждый шаг, который выполнился, с его outcome (match, miss или skipped), машиночитаемой reason, найденными vasp и rail, а также временной меткой. VASP, которого выбрали вы или конечный пользователь, имеет приоритет над любым автоматическим шагом: поиск контрагента никогда не подменяет его собственной догадкой. Если ни один шаг не называет контрагента, перевод переходит в статус COUNTERPARTY_VASP_NOT_FOUND, а если VASP был назван, но ни один из его маршрутов не доступен вашему приложению, то в статус COUNTERPARTY_VASP_NOT_REACHABLE. Затем перевод получает reason (awaiting_end_user, no_directory_match, directory_match_no_route, или picked_member_without_proven_wallet, если выбранный конечным пользователем участник не подтвердил владение кошельком) и список next_actions: Ссылка на виджет находится в поле url для действий, которые выполняет конечный пользователь. Когда ожидается действие по подтверждению владения кошельком, блок action_required транзакции (тип wallet_ownership) содержит то же reason и next_actions, поэтому клиент, который уже следует action_required, покажет выбор без дополнительной работы.

Поля определения контрагента

Всё, что Didit известно о контрагенте, берётся из travel_rule_details.beneficiary_data. Эти поля управляют определением канала; все они необязательны:

Типы цепочек

В цепочке на основе аккаунтов (EVM-сети, Solana, Tron) перевод перемещает средства с одного адреса на другой, и Didit считывает оба кошелька непосредственно из транзакции. Три других типа требуют дополнительных данных, и то, что Didit определил для них, хранится в переводе в поле chain_details (пустое, {}, в цепочке на основе аккаунтов). chain_details.originator и chain_details.beneficiary содержат address, его memo и источник (source), из которого он получен; сопоставление и маршрутизация сначала используют кошельки, указанные там.

Переводы UTXO

Транзакция в Bitcoin, Litecoin, Bitcoin Cash или Dogecoin расходует входы с любого количества адресов и выплачивает любое количество выходов, часть которых возвращается отправителю в виде сдачи. Передавайте входы и выходы в travel_rule_details.utxo (суммы в единицах самой монеты, в виде десятичных строк), и Didit выведет то, что вы не указали:
  • Отправитель: originator_data.wallet_address, если вы его передали, иначе адрес входа, который внёс наибольший вклад (source: largest_input).
  • Получатель: указанный кошелёк, иначе единственный выход, который не возвращает сдачу на адрес входа (only_non_change_output), иначе один выход, сумма которого равна сумме перевода (amount_match).
Затем перевод содержит:
chain_details.warnings сигнализирует о несовпадениях: declared_originator_not_an_input, declared_beneficiary_not_an_output, beneficiary_output_ambiguous (ни один выход нельзя было однозначно отличить) и beneficiary_output_amount_differs. Указание utxo в любой другой сети приводит к ошибке 400, а каждый список содержит не более 1,000 записей. Для сообщённого депозита используется тот же объект utxo (см. Регистрация после депозита).

Сети с memo и тегами

В сетях XRP, Stellar (XLM), EOS, Cosmos (ATOM) и Hedera (HBAR) биржа получает всех клиентов на одном общем адресе и различает их по destination tag или memo. Поэтому memo является частью кошелька: передайте его как payment_method.memo у участника или как memo в originator_data / beneficiary_data (псевдоним destinationTag). Didit сравнивает адрес и memo вместе.
  • Запись адресной книги с memo совпадает только с переводами, в которых указано это memo. Запись без memo совпадает со всем адресом. Перевод без memo никогда не попадает в запись, которая его имеет: на общем адресе депозит без тега никому не принадлежит.
  • X-адрес XRP содержит тег внутри себя: Didit разделяет его на классический адрес и тег (source: x_address), поэтому кошелёк, зарегистрированный в любой из этих форм, сопоставляется с переводом в другой форме.
  • Каждое memo на общем адресе является отдельным способом оплаты в транзакции.
  • Что принимает каждая сеть: destination tag XRP - целое число от 0 до 4294967295 (допускается число в JSON); memo Stellar - текст до 28 байт, 64-битный идентификатор или 32-байтовый хеш в hex; EOS принимает до 256 байт, Cosmos до 256 символов, Hedera до 100 байт. memo в сети, которая его не поддерживает (Bitcoin, EVM-сети, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin, Lightning), возвращает 400.

Lightning

У платежа Lightning нет адреса получателя в блокчейне: плательщик владеет запросом на платёж. В криптопереводе bitcoin или Lightning payment_method.account_id может быть этим запросом, и Didit определяет получателя по нему, не обращаясь ни к одной сети: Получатель сохраняется как аккаунт и как chain_details.beneficiary (source: invoice_payee) в сети LIGHTNING, а то, что сообщает запрос, находится в chain_details.lightning. Если запрос не называет получателя, исходящий перевод получает статус NOT_APPLICABLE, а причина указывается в chain_details.not_applicable_reason и в его timeline: LIGHTNING_NO_PAYEE_IDENTIFIER, LIGHTNING_BOLT12_BLINDED_PAYEE или LIGHTNING_LNURL_NAMES_NO_PAYEE. Значение считается Lightning только в сети bitcoin, в LIGHTNING или когда сеть не указана (Lightning Address только в bitcoin или LIGHTNING). В любой другой сети это собственный адрес этой сети.
Канал INTERNAL достигает другого клиента Didit только через кошелёк, контроль над которым клиент подтвердил подписанным сообщением или тестовым депозитом. Такие подтверждения сегодня существуют для Bitcoin, EVM-сетей, Solana и Tron. Поэтому в сетях с memo, Lightning, Litecoin, Bitcoin Cash и Dogecoin перевод никогда не достигает другого клиента Didit через INTERNAL. Сопоставление по memo, тегам и Lightning по-прежнему применяется к вашей собственной адресной книге: входящие переводы, сообщённые депозиты и запросы TRP к вашим кошелькам.

Обновление кошелька отправителя

Вывод средств часто объявляется с основного кошелька вашего клиента, а транслируется с тех адресов, на которых находились монеты. Запишите кошелёк, с которого средства фактически ушли, через PATCH /v3/transactions/{transaction_id}/travel-rule/:
  • Только для исходящих переводов, и никогда для переводов со статусом CANCELLED или EXPIRED (409).
  • Если входы UTXO перевода известны, кошелёк должен быть одним из них (иначе 400). В сети с memo отправьте также memo.
  • Изменяется только запись: контрагенту ничего не отправляется повторно, статус сохраняется. chain_details.originator становится новым кошельком с source: updated, в timeline фиксируются предыдущий и новый кошелёк, а встречная запись INTERNAL перевода обновляется так же.

Атрибуция в блокчейн-аналитике

Если вы передаёте только адрес кошелька, Didit всё равно может определить, кто находится на другой стороне. Если ни один канал не может маршрутизировать перевод, Didit сопоставляет кошелёк назначения с данными атрибуции из блокчейн-аналитики: тот же анализ, на котором работает скрининг криптотранзакций, знает, какие кошельки принадлежат каким биржам и кастодианам. Атрибуция выполняется только для исходящих переводов, которые содержат и кошелёк назначения, и chain (см. Поля определения контрагента). Без указания сети она пропускается. Если кошелёк атрибутирован известной сущности, Didit:
  1. Фиксирует атрибуцию в переводе: beneficiary_data.counterparty_vasp_name заполняется (если вы не передали его сами), а метаданные перевода содержат блок vasp_attribution с названием сущности и типом сущности, страной и source: "blockchain_analytics".
  2. Добавляет организацию в справочник VASP (или сопоставляет с существующей записью), чтобы атрибуция оставалась стабильной между переводами.
  3. Повторно запускает каналы один раз с атрибутированным названием. Запись справочника, уже содержащая endpoint TRP, сразу направляет перевод по маршруту, вместо того чтобы он попадал в COUNTERPARTY_VASP_NOT_FOUND.
Вызов атрибуции одновременно выполняет полный скрининг криптотранзакции: транзакция получает полный результат скрининга (оценку риска, категорию риска кошелька, снимок сети) точно так же, как если бы работал криптомониторинг. custom_values.aml_screening_trigger транзакции устанавливается в travel_rule_attribution, чтобы вы видели, что скрининг был запущен, потому что этого потребовала маршрутизация Travel Rule. Если криптомониторинг уже проверил тот же кошелёк в этой транзакции, сохранённый результат используется повторно: без второго вызова аналитики и без второго списания. Атрибуция никогда не блокирует создание транзакции: если запрос к аналитике завершается ошибкой или на вашем балансе не хватает средств, перевод просто переходит в COUNTERPARTY_VASP_NOT_FOUND, и за попытку ничего не списывается. При action: "resend" ранее записанная атрибуция используется повторно, а не запрашивается заново. Чтобы отключить весь шаг, укажите vasp_attribution_enabled: false в настройках. См. Цены, чтобы узнать, как тарифицируется вызов атрибуции. Когда Travel Rule отключён, travel_rule_details (включая переданный вами status, устаревший или текущий) всё равно записывается в транзакцию, но пассивно: никакой канал не задействуется. Используйте это, чтобы дозаполнить данные комплаенса, которые вы уже вели по собственному процессу. Пассивный исходящий перевод не запускает обмен, поэтому он тарифицируется как Transaction Screening, а не по строке Travel Rule. При включённом Travel Rule этот запрос идёт по каналу EMAIL, поскольку в нём указан адрес электронной почты комплаенса контрагента, а ни один из предыдущих каналов не может связаться с получателем:
Ответ, сокращённый до полей, которые здесь важны:
  • originator_data содержит то, что вы отправили, потому что у субъекта в примере нет одобренной верификации KYC; для проверенного пользователя Didit формирует его из проверенных данных (см. Проверенные данные и их происхождение). Канал EMAIL показывает получателю только те поля отправителя, которыми вы делитесь в data_sharing_fields.
  • beneficiary_data.chain равен ETH, так как в запросе сеть не была указана и Didit определила её по валюте.
  • status равен APPROVED, так как ни одно правило не повлияло на транзакцию; ваши собственные правила могут установить другой статус.
  • Контакт комплаенса получает ссылку для получения, и перевод завершается, не проходит проверку совпадения имени или отклоняется, когда он отвечает, либо истекает, если он не отвечает (см. Получение через email).
protocol остаётся null, потому что канал выбирается после ответа (например, EMAIL, если вы передали counterparty_vasp_email): Получить транзакцию показывает его после выбора, а каждое последующее событие webhook travel_rule.status.updated содержит его в rail. Если кошелёк получателя принадлежит другому клиенту Didit, который подтвердил над ним контроль, канал INTERNAL завершает весь обмен сразу после ответа, и этот webhook уже сообщает COMPLETED (или COUNTERPARTY_MISMATCHED_DATA). Если имя не совпадает, ваш перевод получает COUNTERPARTY_MISMATCHED_DATA, а другой клиент ничего не получает. Без counterparty_vasp_email, и когда ни каналы, ни атрибуция не могут связаться с получателем, перевод позже завершится со статусом COUNTERPARTY_VASP_NOT_FOUND и действием wallet_ownership в транзакции.
Встроенный объект travel_rule в транзакции содержит собственные поля перевода (uuid, status, protocol, required, obligations_count, originator_data, beneficiary_data, metadata), то, что Didit определила и сравнила (chain_details, name_match, data_match, mismatched_fields, а также поля маскирования data_masked и data_unmasked), и поля, которые показывают текущее состояние: direction, rail, counterparty_vasp, delivery, discovery, reason и next_actions (см. Почему перевод застрял). Каждая строка GET /v3/transactions/ содержит такой же блок или null для транзакции без перевода. Отдельные эндпоинты переводов, описанные ниже, тоже возвращают эти поля, а также ivms_version, ownership_confirmed, deadline_at, timeline и created_at, но без obligations_count и metadata.
Чтобы закрыть исходящий перевод в статусе COMPLETED после того, как вы отправили его в блокчейн:
Требуется, чтобы перевод был исходящим переводом в статусе COMPLETED, иначе API возвращает 409. При успехе и исходящий перевод, и его парный перевод в канале INTERNAL переходят в FINISHED, а payment_txn_id транзакции получает хеш, который вы передали. В канале TRP Didit также отправляет хеш на обратный вызов подтверждения контрагента. Чтобы отменить перевод, который ещё не достиг финального статуса, отправьте вместо этого {"action": "cancel"} на тот же эндпоинт. Парный перевод в канале INTERNAL тоже отменяется, а в канале TRP, после того как контрагент одобрил запрос, Didit отправляет отмену на его обратный вызов подтверждения. Перевод, уже находящийся в FINISHED, CANCELLED или EXPIRED, возвращает 409. Запрос без payment_txn_id и без поддерживаемого action возвращает 400. Для перевода, отправленного по GTR, VERIFYVASP или SYGNA, Didit сообщает хеш (или отмену) в сеть после ответа, с теми же повторными попытками; поле delivery в ответе показывает это сообщение. Чтобы повторить исходящий перевод, который не смог дойти до контрагента, то есть находящийся в COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE или NOT_ENOUGH_COUNTERPARTY_DATA, сначала улучшите данные о контрагенте: например, добавьте travel_address в запись кошелька назначения, контакт для записи контрагента в каталоге или добавьте VASP в каталог. Затем отправьте {"action": "resend"} на тот же эндпоинт. Didit заново отсчитывает срок подтверждения и заново ставит в очередь лестницу маршрутизации с самого начала, используя данные, сохранённые в переводе: ответ показывает, что перевод снова в AWAITING_COUNTERPARTY (с resend queued в его timeline), а результат приходит как webhook travel_rule.status.updated. Повторный запрос, отправленный во время выполнения предыдущей попытки, будет обработан после завершения этой попытки. Любой другой статус или входящий перевод возвращает 409.
Чтобы назначить контрагента самостоятельно, если обнаружение не смогло это сделать, отправьте {"action": "beneficiary_vasp", "beneficiary_vasp": "<directory VASP id>"} (id из GET /v3/travel-rule/vasps/). Перевод направляется к этому VASP и отправляется снова. Это действие доступно только для исходящего перевода в COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE или NOT_ENOUGH_COUNTERPARTY_DATA; для любого другого статуса возвращается 409, а для неизвестного id возвращается 404.

Почему перевод застрял

Перевод в COUNTERPARTY_VASP_NOT_FOUND или COUNTERPARTY_VASP_NOT_REACHABLE сообщает, почему он застрял и что позволяет ему продвинуться дальше, при каждом запросе его транзакции. Блок travel_rule в GET /v3/transactions/{transaction_id}/, в ответе 201 на POST /v3/transactions/ и в каждой строке GET /v3/transactions/ содержит его, так же как и объект перевода, который возвращает эндпоинт переводов:
  • reason содержит машиночитаемую причину, "" для любого другого статуса.
  • next_actions содержит список действий, которые продвигают перевод дальше, [] для любого другого статуса.
  • delivery содержит самое новое сообщение, которое перевод должен отправить другой стороне: kind, status (QUEUED, SENDING, RETRYING, DELIVERED или FAILED), rail, attempts, last_error, next_attempt_at и delivered_at, либо null, если ему нечего было отправлять.
  • discovery показывает, где Didit искал контрагента, по одной записи на каждый выполненный шаг: step, outcome (match, miss или skipped), reason, vasp, rail и at.
email_undeliverable и delivery_failed возникают из-за неудачной доставки и появляются только при COUNTERPARTY_VASP_NOT_REACHABLE. Сбой доставки в первую очередь определяет reason, поэтому контрагент, которого Didit нашёл, никогда не будет указан как тот, которого найти не удалось. Остальные четыре значения берутся из трассировки поиска, при любом статусе. Повторная отправка продвигает перевод дальше, поэтому его reason очищается. Запись в таймлайне о переходе, который вызвала неудачная доставка, содержит то же значение в поле reason. Если почтовый провайдер отклонит комплаенс-письмо для примера перевода выше, блок travel_rule в его транзакции выглядит так (в сокращении):
Здесь delivery согласуется с reason, потому что отправка сама по себе была отклонена. Отказ при напоминании или сообщение о постоянной недоставке, полученное позже, оставляет delivery.status в значении DELIVERED: первое письмо действительно ушло. Чтобы понять, почему перевод застрял, смотрите reason, а не delivery. Чтобы исправить ситуацию, передайте адрес электронной почты контрагента для комплаенса повторно, а затем выполните повторную отправку:
update_compliance_email сохраняет адрес как beneficiary_data.counterparty_vasp_email этого перевода: он относится только к этому переводу и не добавляется ни в одну запись справочника или список контактов. Статус не меняется, в таймлайн добавляется запись compliance email updated, а перевод сохраняет reason: email_undeliverable, пока повторная отправка не отправит запрос на новый адрес. Этот запрос принимает только исходящий перевод в статусе COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE или NOT_ENOUGH_COUNTERPARTY_DATA; любой другой перевод возвращает 409, а отсутствующий или некорректный compliance_email возвращает 400.

Объявленные некастодиальные кошельки

Если ваш клиент сообщает, что кошелёк назначения является его собственным некастодиальным (unhosted) кошельком, объявите это в транзакции, а не ждите, пока маршрутизация выяснит, что за кошельком нет VASP: задайте тип способа оплаты контрагента unhosted_wallet.
Объявление полностью исключает маршрутизацию:
  • Ни один канал не используется: контрагента-VASP, с которым можно обменяться данными, нет, и перевод сразу создаётся в статусе UNCONFIRMED_OWNERSHIP.
  • Если auto_wallet_verification включён (по умолчанию), Didit автоматически создаёт сессию виджета подтверждения владения кошельком, привязанную к транзакции, и возвращает её в блоке action_required транзакции; SDK запускают её автоматически при отправке на стороне клиента.
  • Как только клиент подтверждает владение кошельком, перевод выполняет сверку имени получателя и переходит в COMPLETED (или в COUNTERPARTY_MISMATCHED_DATA, если сверка не прошла).
Это требование EU TFR к некастодиальным кошелькам в виде API: проверить владение некастодиальным кошельком вместо обмена данными с контрагентом-VASP. Объявление читается из способа оплаты на стороне получателя исходящего перевода и на стороне источника входящего: в обоих случаях это участник-контрагент. Объявленный некастодиальный перевод уже находится на финальном пути без каналов, поэтому UNCONFIRMED_OWNERSHIP не является состоянием, для которого допустим повторный запуск: завершите подтверждение владения (или отмените перевод) вместо вызова action: "resend".

Получение

Подтверждение владения кошельком

Перевод ожидает в статусе UNCONFIRMED_OWNERSHIP в двух случаях:
  • Входящий перевод сопоставляется с записью в адресной книге, владение которой ещё не подтверждено.
  • Кошелёк контрагента заявлен как некастодиальный (counterparty.payment_method.method_type: "unhosted_wallet") при исходящем выводе средств или входящем зачислении. См. Заявленные некастодиальные кошельки.
Ваше приложение подтверждает или отклоняет перевод:
Тело запроса принимает confirmed (обязательный, логического типа) и необязательный wallet_address. Endpoint принимает перевод в любом из следующих статусов и возвращает 409 с {"detail": "Transfer is not awaiting ownership confirmation."} для любого другого статуса:
  • UNCONFIRMED_OWNERSHIP, в любом направлении.
  • ON_HOLD для входящего перевода.
  • UNCONFIRMED_OWNERSHIP, ON_HOLD или COUNTERPARTY_MISMATCHED_DATA для входящего перевода TRP, чтобы комплаенс-офицер мог вручную обработать запрос контрагента.
Для перевода, который Didit создал сам (канал INTERNAL, регистрация sunrise или заявленный некастодиальный кошелёк):
  • confirmed: true помечает соответствующую запись в адресной книге как подтверждённую в отношении владения, затем проводит сверку имени с полем holder_name этой записи: COMPLETED, если проверка пройдена, и COUNTERPARTY_MISMATCHED_DATA, если нет. Соответствующая сторона перевода по каналу INTERNAL, если она есть, получает тот же результат. Для заявленного некастодиального кошелька confirmed: true фиксирует, что ваш клиент подтвердил, кто контролирует кошелёк контрагента, и завершает перевод (COMPLETED), исходящий или входящий. Входящий перевод, зачисление по которому уже произведено, сразу переходит в FINISHED.
  • confirmed: false переводит обе стороны перевода в COUNTERPARTY_VASP_GENERAL_DECLINE.
  • Соответствующая запись ищется в вашей адресной книге только по адресу кошелька, без учёта блокчейна. Подтверждение входящего перевода в ON_HOLD без соответствующей записи создаёт запись из beneficiary_data.wallet_address и помечает её как подтверждённую. Исключение составляет заявленный некастодиальный кошелёк: он регистрируется в адресной книге как подтверждённый по владению, как описано ниже. Во всех остальных случаях отсутствие записи возвращает 409 с {"detail": "wallet address entry no longer available"}. Такой же ответ возвращает и удалённая запись.
Для входящего перевода TRP тот же вызов отвечает на запрос контрагента:
  • confirmed: true требует платёжный адрес, который берётся из wallet_address в запросе, затем из найденной записи и, наконец, из beneficiary_data.wallet_address. Если ни один из трёх не задан, запрос возвращает 400 с {"wallet_address": ["wallet_address is required to approve."]}. Didit создаёт или подтверждает запись, отправляет контрагенту callback с решением по запросу, устанавливает ownership_confirmed и переводит перевод в COMPLETED.
  • confirmed: false отправляет ответ об отказе и переводит перевод в COUNTERPARTY_VASP_GENERAL_DECLINE.
  • Если callback endpoint контрагента недоступен, запрос возвращает 502, а перевод сохраняет текущий статус. При одобрении запись в адресной книге кошельков всё равно создаётся или помечается как подтверждённая.
Для заявленного некастодиального кошелька вы подтверждаете кошелёк контрагента: получатель исходящего вывода или источник входящего зачисления (кошелёк отправителя). Didit добавляет его в адресную книгу кошельков как подтверждённый по владению на имя контрагента, поэтому кошелёк сверяется с именем контрагента (отправителя в случае зачисления). Если в адресной книге этот кошелёк уже привязан к другому имени держателя, перевод переходит в COUNTERPARTY_MISMATCHED_DATA. Если в переводе не указан кошелёк контрагента, передайте wallet_address в теле запроса. Если ни перевод, ни запрос не содержат адрес кошелька, запрос отклоняется с кодом 400.
  • Сетевой перевод, на который уже ответили через его сеть (перевод VerifyVASP или перевод GTR либо Sygna Bridge, по которому уже принято решение), возвращает 409 (см. Переводы из сети).
Ответ содержит расширенный объект перевода, описанный выше. Новый перевод INTERNAL здесь никогда не ожидает: он доступен только клиенту, который уже подтвердил владение кошельком. Старый перевод INTERNAL, который всё ещё ждёт с тех времён, когда этого правила не было, скрывает данные отправителя, пока вы не докажете контроль над кошельком (подписанным сообщением или тестовым зачислением). Подтверждение такого перевода возвращает 409, пока вы этого не сделаете. Отклонить его по-прежнему можно. Если контрагент сообщил, что кошелёк некастодиальный (сейчас в песочнице такой ответ даёт Didit Test VASP), подтверждение добавляет кошелёк в вашу адресную книгу на имя получателя и запускает сверку имени: при совпадении перевод завершается, а кошелёк помечается как подтверждённый.

Переводы из сети

Didit также может принимать переводы Travel Rule, которые контрагент отправляет по сетям GTR, VERIFYVASP или SYGNA. Didit отдельно включает приём для каждой сети, и сейчас он не включён ни для одной: для этого также нужна регистрация endpoint’ов Didit в каждой сети, а членства Didit в этих сетях пока нет. Переводы TRUST принять нельзя. Когда сеть включена, сообщение, которое она доставляет, обрабатывается так:
  • Маршрутизация. Сообщение попадает в ваше приложение, если в нём указан код участника вашего подключённого членства в этой сети. Сообщение, адресованное членству самого Didit, попадает к вам только в том случае, если вы являетесь единственным действующим клиентом Didit, подтвердившим контроль над кошельком получателя. Всё остальное отклоняется, поэтому ни один клиент никогда не получает перевод другого клиента.
  • Решение. Didit создаёт входящий перевод в вашем приложении и принимает по нему решение так же, как по запросу TRP: сопоставление кошелька, владение, сверка имени и inbound_auto_accept.
  • Однократность. Сети повторно отправляют сообщение, пока на него не ответят. Повтор получает тот же ответ, что и первая копия, и ничего не создаётся и не решается дважды.
  • Ваша проверка по GTR и SYGNA. Решение по переводу, который ждёт вашего решения (UNCONFIRMED_OWNERSHIP, ON_HOLD или COUNTERPARTY_MISMATCHED_DATA), принимается с помощью POST .../travel-rule/ownership/: confirmed: true одобряет его (передайте wallet_address, если в переводе он не указан), а confirmed: false отклоняет. Didit отвечает сети после ответа endpoint, с теми же повторными попытками. Перевод, на который уже ответили, возвращает 409.
  • Без отложенного ответа по VERIFYVASP. VerifyVASP получает ответ в момент запроса, поэтому перевод, которому потребовалась бы ваша проверка, сразу отклоняется и позже решить его нельзя (409).

Маскирование данных контрагента

Пока владение кошельком не подтверждено, персональные данные контрагента во входящем переводе возвращаются замаскированными, а не скрытыми полностью. Первая буква каждого слова сохраняется, а кошелёк, сеть и маршрутизация через VASP остаются читаемыми. data_masked равно true, и контрагент в деталях транзакции, в списке и в экспорте CSV маскируется так же. После подтверждения владения отображаются реальные значения, а запись аудита фиксируется в data_unmasked:
reason принимает значения ownership_confirmed, wallet_proof, address_book_match, own_verified_wallet, inquiry_approved (вы одобрили запрос TRP или сообщение GTR или Sygna), known_payment_method (кошелёк конечного пользователя из ваших записей ответил) или travel_address (Travel Address для кошелька указал на этот кошелёк). Входящий перевод INTERNAL сразу при создании получает data_unmasked с reason: wallet_proof, так как такую запись получает только получатель, который доказал владение кошельком. actor содержит email пользователя консоли или метку API-ключа, а для автоматического решения остаётся пустым. Отчёт Журнал переводов по Travel Rule (CSV и PDF) маскирует имена во входящем переводе так же, пока владение не подтверждено. Отчёты, которые вы подаёте регулятору, например goAML XML и регуляторный отчёт в PDF, содержат реальные данные контрагента.

Обратная связь о расхождениях

Перевод со статусом COUNTERPARTY_MISMATCHED_DATA теперь указывает поля, которые не совпадают. data_match содержит результат сравнения, а mismatched_fields перечисляет поля. Оба параметра содержат только названия полей и результаты, но никогда значения, поэтому остаются читаемыми при маскировании данных:
Запись хронологии называет поля, например «дата рождения получателя не совпадает с владельцем кошелька». После расхождения отказ по TRP сообщает VASP-отправителю, какие поля различаются, например: «Данные получателя не совпадают с владельцем кошелька: имя». Объём информации, которую получает другая VASP, сознательно ограничен: пока имя не совпадает, сообщается только это поле (mismatched_fields: ["name"], а все остальные поля имеют значение not_compared). Когда имя совпадает, называются все поля, которые различаются. Это относится к стороне отправителя при обмене INTERNAL и к отказу по TRP. Имя, отсутствующее с обеих сторон, получает значение not_compared и не считается расхождением. Страна сравнивается по тому, что обозначает, поэтому ES и ESP совпадают.

Оценка совпадения имён

Каждый перевод, в котором сравнивалось имя, содержит name_match, а запись хронологии, которая определила результат, содержит тот же объект рядом со своим detail:
  • score означает сходство полных имён от 0 до 100; если одно из имён пустое, значение равно 0. matched определяется результатом при уровне strictness, поэтому эти два значения могут расходиться: FUZZY решает на основании собственного коэффициента сходства 0.85.
  • matched_names: слова имени перевода, найденные в другом имени. mismatched_names: слова, которые есть у одной стороны, но отсутствуют у другой.
  • method принимает значения none, exact_token, token_subset, token_containment или sequence_ratio для NONE, STRICT, DEFAULT, CONTAINS и FUZZY соответственно.
  • compared_with принимает значения wallet_holder (владелец, записанный для кошелька) или counterparty_vasp (имя, которое контрагент указал на странице ответа по email, или ответ Didit Test VASP).
  • Исход перевода определяется data_match, который сравнивает все поля, поэтому перевод может иметь статус COUNTERPARTY_MISMATCHED_DATA, при этом name_match.matched равно true.
  • name_match есть в объекте перевода, который возвращают эндпоинты Travel Rule, и во встроенном объекте travel_rule транзакции, и он равен null, пока имя не сравнивалось. Список переводов и webhook’и этот объект не содержат.
  • Имена скрыты, пока стороны замаскированы, а также в копии обмена INTERNAL у другого клиента.

Сообщения и запросы документов

Два VASP могут разрешить спорный перевод внутри Didit, а не по email. GET и POST /v3/transactions/{transaction_id}/travel-rule/messages/ выводят список сообщений по переводу и отправляют новые:
  • kind принимает значения MESSAGE (по умолчанию) или DOCUMENT_REQUEST. Запрос документов требует хотя бы одну запись в requested_documents (до 10); сообщение требует body (до 5000 символов) или файл.
  • reply_to указывает на более раннее сообщение того же перевода. Ответ другой стороны с документом выполняет запрос документов, на который он отвечает, и в нём устанавливается fulfilled_at. Ваш собственный ответ никогда не выполняет ваш запрос.
  • Отправляйте multipart с files, чтобы прикрепить до 5 файлов размером до 10 МБ каждый в форматах PDF, PNG, JPEG или WebP. Didit проверяет содержимое каждого файла, а не только заявленный тип. При загрузке любого другого файла запрос возвращает 400 с ошибкой в поле files: «Attach PDF, PNG, JPEG or WebP files only.».
  • Перевод вмещает до 100 сообщений, из них до 20 могут содержать вложения (до 5 файлов в каждом), с учётом обеих сторон. При превышении любого из этих лимитов запрос возвращает 400. Вложения в сообщениях не учитываются в документах самой транзакции.
  • Сообщения работают по каналам INTERNAL и EMAIL. При INTERNAL другой клиент Didit видит сообщение на своей стороне пары. При EMAIL контрагенту отправляется email, и он отвечает на портале получения. Если уведомление по email не удалось отправить, сообщение всё равно сохраняется, а запрос возвращает 201.
  • Запрос возвращает 409 с причиной в detail для перевода по TRP, GTR, TRUST, VerifyVASP или Sygna (эти протоколы не передают сообщения), для перевода без контрагента, для перевода INTERNAL, данные которого не совпали с владельцем кошелька (получатель ничего не получил), для перевода INTERNAL, где получатель ещё не доказал контроль над кошельком (с обеих сторон, пока он не докажет), для перевода EMAIL, у контрагента которого больше нет адреса email для комплаенса, и для перевода EMAIL, где ссылка на портал получения истекла или перевод закрыт.
  • Webhook не срабатывает, когда контрагент отвечает. Читайте эндпоинт сообщений.
  • Письмо контрагенту о новом сообщении отправляется на английском языке.

Адресная книга кошельков

Регистрируйте кошельки, которыми вы управляете, чтобы входящие переводы (канал INTERNAL, входящие запросы TRP и регистрации после депозита) могли сопоставляться с ними. Используйте POST/GET /v3/travel-rule/wallet-addresses/ и PATCH/DELETE /v3/travel-rule/wallet-addresses/{entry_uuid}/.
PATCH принимает holder_name, holder_vendor_data, entity_type и travel_address и возвращает только эти четыре поля. DELETE удаляет запись (204): её адрес освобождается для повторной регистрации, и она сразу перестаёт сопоставляться с новыми переводами.
Сопоставление нечувствительно к регистру адреса, учитывает его memo в сети с memo и ограничено chain. Если перевод содержит сеть, запись сопоставляется только тогда, когда её chain совпадает с ним (без учёта регистра) или пуст. Названия сетей сравниваются как текст, без перевода, поэтому запись, сохранённая как ethereum, не совпадает с переводом, у которого сеть ETH. Исходящие переводы берут сеть из beneficiary_data.chain, затем из transaction_details.crypto_params.crypto_chain, затем из custom_properties.crypto_chain, затем из валюты (например, ETH, а для USDT и USDC сеть определяется по payment_reference_id). Значение учитывается, только если Didit распознаёт эту сеть. Регистрации после депозита берут сеть из поля chain в нижнем регистре. Входящие запросы TRP не учитывают сеть: они сопоставляются по адресу или, если в запросе не назван кошелёк, по имени получателя среди ваших подтверждённых записей. POST .../travel-rule/ownership/ также сопоставляет только по адресу. Если совпадают несколько записей, канал INTERNAL и регистрации после депозита предпочитают запись с подтверждённым владением, затем самую старую, а поиск исходящего TRP берёт самую старую запись, в которой сохранён Travel Address. Входящие запросы TRP и эндпоинт владения берут любую из совпадающих записей, поэтому по возможности держите одну запись на один адрес. Канал INTERNAL не сопоставляет ни одной записи, если подтверждённые записи для адреса существуют более чем в одном другом приложении.

Массовый импорт кошельков

Импортируйте до 10 000 кошельков одним вызовом POST /v3/travel-rule/wallet-addresses/import/ в формате JSON ({"entries": [...]} или простой массив) или как файл CSV в кодировке UTF-8 размером не более 5 МБ со строкой заголовков. Строка задаёт кошелёк через address, через address_hash (SHA-256 адреса в нижнем регистре, 64 шестнадцатеричных или 44 символа base64) либо через оба поля, а также любые поля записи, перечисленные выше. Чтобы зарегистрировать кошелёк, не раскрывая его адрес, передайте только хеш: такая запись сопоставляется с переводами на этот адрес, как любая другая, и её владение можно подтвердить через виджет подтверждения владения кошельком.
Импорт идемпотентен и сообщает о каждой строке в порядке отправки: created, existing (тот же кошелёк, сеть и memo уже есть в реестре и не были изменены), duplicate (повторяет более раннюю строку, которая указана в duplicate_of_row) или failed с полем errors. Ошибка в одной строке не влияет на остальные: ответ имеет статус 200, если ни одна строка не завершилась ошибкой, и 207, если хотя бы одна завершилась; счётчики находятся в total, created, existing, duplicate и failed. Пустая ячейка CSV считается не переданной, а столбцы, которые импорт не читает, игнорируются, поэтому экспорт из другого инструмента импортируется как есть. В ответе GET /v3/travel-rule/transfers/ каждая строка содержит originator_memo и beneficiary_memo рядом с двумя кошельками.

Отправка по email контрагентам, которые недоступны

Если ни один протокольный канал не может связаться с контрагентом, Didit переходит на EMAIL: он отправляет одноразовую ссылку для получения перевода на beneficiary_data.counterparty_vasp_email (или на email, найденный по beneficiary_data.counterparty_vasp_name в реестре VASP). Получателю не нужна учётная запись Didit или API-ключ: аутентификацией служит токен в ссылке. Как отправляется запрос:
  • На правильные адреса. Переданный вами counterparty_vasp_email – единственный получатель. Иначе Didit пишет на до трёх адресов: контакты контрагента, на которые ранее не приходили возвраты (сначала ваши, затем контакты платформы, затем те, что отвечали раньше), а затем email из записи реестра, если остался свободный слот. Каждый адрес получает своё письмо.
  • От вашего имени. Письмо приходит с адреса Didit в виде «ваше юридическое название via Didit» на языке юрисдикции контрагента (английский, если она неизвестна). Ссылка открывает страницу ответа на этом языке. Копия вам не приходит: отслеживайте хронологию перевода.
  • С напоминаниями. Пока контрагент не ответил, Didit напоминает ему за 72, 24 и 6 часов до срока, не раньше чем через 12 часов после запроса или последнего напоминания, и никогда не после срока.
  • С обработкой возвратов. Возврат письма или жалоба на спам помечают контакт, до которого дошли, как недействительный, с указанием причины, и хронология перевода это фиксирует. Контакт платформы тогда недействителен для всех клиентов Didit, а собственный контакт вашего приложения – только для вашего приложения. Если Didit полностью прекращает отправку email на адрес (после постоянного возврата, жалобы на спам или отписки), каждый контакт по этому адресу помечается как недействительный (suppressed), и эти контакты снова становятся действительными, когда отправка на адрес возобновляется. Если не осталось адреса для отправки, перевод переходит в статус COUNTERPARTY_VASP_NOT_REACHABLE, а причина от почтового сервера получателя записывается в хронологию. Постоянный возврат или жалоба на спам на переданный вами counterparty_vasp_email приводят к этому сразу, поскольку это единственный получатель. Временный возврат (soft_bounce) на него или на собственный compliance email записи реестра оставляет запрос открытым для напоминаний.
Ссылка открывает размещённую страницу, на которой показан ожидающий обмен. Для получателя страница обращается к двум публичным endpoint’ам. Для примера перевода выше данные для получения выглядят так:
Поле reference содержит uuid перевода. originator_data и IVMS 101 ivms_request, собранный из него, содержат только поля отправителя, которыми вы поделились через data_sharing_fields. Имя и кошелёк включаются всегда. fiat_amount и fiat_currency пусты, если вы не отправляли конвертированную сумму. contact повторяет значение ?contact= из ссылки, а decline_reasons перечисляет значения, которые принимает decline_reason. join_invitation.eligible равно true, пока у контрагента есть действующее приглашение присоединиться к Didit. Сама ссылка на приглашение передаётся только в письме. Этот endpoint возвращает сохранённую сумму без изменений, поэтому в ней 18 знаков после запятой, а не сокращённый вид, который используется в объекте деталей транзакции.
decision принимает значение accept или decline. При accept переданный beneficiary_name сравнивается с beneficiary_data.name перевода по вашему правилу проверки имени: при совпадении статус становится COMPLETED, иначе COUNTERPARTY_MISMATCHED_DATA. Контрагент может вместо этого передать полный ответ IVMS 101 в поле ivms_response. Тогда имя получателя из него имеет приоритет над beneficiary_name. При decline перевод переходит в статус COUNTERPARTY_VASP_GENERAL_DECLINE. Контрагент может указать decline_reason из decline_reasons и добавить decline_detail (до 512 символов, обязательно для other). Ваша хронология перевода сохраняет эту причину, например: counterparty declined: The beneficiary is not our customer. Ответ содержит итоговый {"status": "..."}, а после отказа также decline_reason. После ответа контрагента оба endpoint’а возвращают 410. Оба endpoint’а возвращают 404 для неизвестного токена и 410, когда окно закрыто: либо перевод вышел из статуса AWAITING_COUNTERPARTY, либо истёк срок подтверждения (confirmation_timeout_hours с момента создания или последней повторной отправки). Контрагент читает и отвечает по ссылке для получения с помощью GET и POST /v3/travel-rule/pickup/{token}/messages/. API-ключ для этого не нужен. Принимаются только вложения PDF, PNG, JPEG или WebP. Если ссылка истекла или перевод закрыт, endpoint возвращает 410. Сообщение, которое отправили вы, показывает название вашего VASP в поле author и в поле uploaded_by вложения, а не имя человека или API-ключ, использованный для отправки.

Непривязанные депозиты

Входящий депозит может прийти раньше, чем данные Travel Rule от отправляющего VASP. Такой депозит остаётся в статусе AWAITING_COUNTERPARTY или NOT_ENOUGH_COUNTERPARTY_DATA, пока данные не поступят. Чтобы получить список депозитов, которые всё ещё ждут, используйте API-ключ с привилегией transactions:
Ответ содержит те же строки, что и GET /v3/travel-rule/transfers/, но только входящие депозиты, которые ждут данных от отправляющего VASP. Когда данные пришли отдельным входящим сообщением, сопоставьте их самостоятельно:
{transaction_uuid} указывает на депозит. transaction_id указывает на транзакцию полученного сообщения Travel Rule. Вызов возвращает { "deposit": ..., "message": ... }. Он возвращает 404, если любая из двух транзакций больше не существует, и 409 с travel_rule_match_conflict (“This deposit can no longer be matched.”), если одна из сторон уже перешла в другой статус.

Регистрация депозита задним числом

Когда криптодепозит зачисляется ончейн до того, как состоялся какой-либо обмен Travel Rule (отправляющий VASP был недоступен или вы вносите исторические депозиты задним числом), зарегистрируйте его постфактум через POST /v3/travel-rule/inbound/.
originating_vasp.travel_address должен быть настоящим Travel Address с верной контрольной суммой Travel Address. Заглушка вернёт 400 с сообщением "Invalid travel address.". originating_vasp.lei должен быть пустым или состоять из 20 символов в формате LEI (18 букв или цифр, затем 2 цифры). Любое другое значение вернёт 400 с сообщением "Invalid LEI: expected 20 alphanumeric characters (ISO 17442).". Didit проверяет только формат, а не контрольные цифры ISO 17442. Didit создаёт входящую транзакцию travelRule (или повторно использует входящий депозит, который вы уже отправили с тем же хешем и кошельком, если у него ещё нет перевода по Travel Rule) и сопоставляет перевод с вашей адресной книгой кошельков по адресу и memo:
  • Совпадающей записи нет -> ON_HOLD. Разрешите ситуацию через POST .../travel-rule/ownership/: confirmed: true добавляет кошелёк в адресную книгу как верифицированный и запускает сверку имени. При совпадении перевод переходит в COMPLETED, а затем в FINISHED.
  • Запись есть, но владение не подтверждено -> UNCONFIRMED_OWNERSHIP.
  • Имя получателя не совпадает с полем holder_name записи -> COUNTERPARTY_MISMATCHED_DATA.
  • Запись верифицирована и имя совпадает -> COMPLETED, затем сразу FINISHED (депозит уже зачислен).
Для верифицированной записи, добавленной в примере выше, ответ выглядит так:
txn_id новой транзакции состоит из tr-in-, первых 80 символов txid и 12-символьного хеша от txid и кошелька. created равно true (HTTP 201), если этот вызов зарегистрировал депозит, и false (HTTP 200), если такие же txid и wallet_address уже были зарегистрированы. В этом случае существующий перевод возвращается без изменений. В сценарии с верифицированной записью обе смены статуса происходят до ответа endpoint, поэтому синхронный ответ уже показывает конечный статус FINISHED. Промежуточный шаг COMPLETED при этом всё равно фиксируется в timeline перевода. Каждый переход отправляет webhook travel_rule.status.updated (COMPLETED и FINISHED отправляют по одному). Если Didit создала новую транзакцию, сначала отправляется transaction.created, а затем эти события. Регистрация не взимает плату за Travel Rule или мониторинг. Если включён мониторинг криптовалют и chain поддерживается Didit для скрининга, новый депозит также проходит скрининг и тарифицируется один раз по тарифу Wallet Verification & Screening.

Виджет подтверждения владения кошельком

Чтобы вывести перевод из статуса UNCONFIRMED_OWNERSHIP или заранее верифицировать кошелёк до поступления перевода, создайте размещённый виджет подтверждения владения через POST /v3/travel-rule/widget-session/ и отправьте клиента по возвращённому url. Виджет подтверждает контроль над кошельком подписью сообщения (включая смарт-контрактные аккаунты EVM по ERC-1271 и ERC-6492), тестом Сатоши, скриншотом или самодекларацией. Затем он помечает соответствующую запись адресной книги как верифицированную, чтобы обмен мог продолжиться. Исключение составляет скриншот: он сам по себе никогда не подтверждает кошелёк, поэтому команда комплаенса проверяет его и подтверждает перевод в UNCONFIRMED_OWNERSHIP через POST .../travel-rule/ownership/. См. Виджет подтверждения владения кошельком: там описаны полный процесс, методы подтверждения и важное требование. Виджет нужно открывать в настоящем браузере, но никогда внутри webview мобильного SDK.

Каталог

GET /v3/travel-rule/vasps/ ищет по каталогу VASP: общие записи каталога плюс участники сети Didit. Каждая строка показывает, как ваше приложение может связаться с этим VASP сегодня.
search ищет совпадение с фрагментом названия или псевдонима, точным LEI или доменом (counterparty.example или URL на нём). Сузить результаты можно с помощью параметров: Участники сети Didit не оцениваются, поэтому risk и responsiveness возвращают только записи каталога. Результаты сортируются по названию и разбиваются на страницы с помощью limit (по умолчанию 50) и offset. Неизвестная сеть, код страны, диапазон риска или статус отзывчивости возвращают 400 с указанием поля. GET /v3/travel-rule/vasps/{vasp_id}/ возвращает одну строку по её id. Ваш собственный профиль VASP никогда не появляется в ваших результатах. dd_status, dd_score, dd_score_breakdown, dd_assessed_at, dd_score_semantic, risk_level, responsiveness и essential_checks содержат общую оценку комплексной проверки для записи каталога. decision, entity_screening и questionnaires являются собственными записями вашего приложения о VASP, их не видит ни один другой клиент. Что означает каждое из этих полей, см. в разделе Проверка контрагентов. Проверяйте их через поля правил контрагента, а не через ветвления в коде. Участники сети Didit не оцениваются: dd_status равен COMPLETED, dd_score равен null, risk_level пуст, а разбивка пуста.

Маршруты

routes перечисляет способы, которыми ваше приложение может отправить данные этому VASP, в том порядке, в котором их пробует лестница маршрутизации. reachable равен true, когда хотя бы один маршрут имеет статус available:
  • TRP, если у записи есть TRP endpoint, проверенный каталогом.
  • GTR, TRUST, VERIFYVASP, SYGNA, если сеть указывает VASP под кодом. Маршрут имеет статус available только тогда, когда ваше приложение может отправлять через эту сеть сегодня (via: own_membership для вашего собственного подключённого участия, via: didit_membership для участия Didit). В остальных случаях маршрут имеет статус unavailable с reason: no_membership или reason: network_switched_off и никогда не показывается как способ связаться с VASP. Собственные участия Didit в сетях пока не работают, поэтому сегодня сетевой маршрут доступен только через участие, которое вы подключили в Marketplace.
  • EMAIL, если для записи есть контакт комплаенса.
  • INTERNAL для участника сети Didit.
Строка TRISA появляется в networks и показывает, кто этот VASP в TRISA, но у Didit нет канала TRISA, поэтому она никогда не является маршрутом. Приложение в песочнице отправляет только на Didit Test VASP, поэтому для него недоступен ни один маршрут из каталога: каждый маршрут имеет unavailable с reason: sandbox_application, у каждой строки reachable: false, а reachable=true не возвращает строк.

Бренды и их юрлица

Бренд, который работает через несколько регулируемых юрлиц, представлен одной строкой, в которой entities перечисляет юрлица, получающие его переводы. Запись юрлица ссылается на свой бренд через umbrella. Перевод, в котором указан бренд, уходит одному из его юрлиц, которое выбирается среди всех них: тому, что находится в юрисдикции, указанной в beneficiary_data.counterparty_vasp_jurisdiction; иначе тому, которое привязала атрибуция блокчейн-аналитики к адресу назначения кошелька; иначе единственному юрлицу бренда, если юрисдикция не указана. Только после этого это юрлицо должно быть доступно. Если оно недоступно, перевод никогда не уходит другому юрлицу или самому бренду. Когда ни одно юрлицо не подходит (два подходят одинаково или указанная юрисдикция не совпадает ни с одним из них), используется собственный маршрут бренда, а без него перевод получает статус COUNTERPARTY_VASP_NOT_FOUND. Выбор юрлица, которое получит данные отправителя, никогда не делается наугад и никогда не определяется тем, у какого юрлица случайно оказался контакт. Несколько записей с одинаковым названием, например группы компаний с общим торговым названием, различаются по тому же правилу: берётся та, что находится в указанной юрисдикции, иначе та, которую привязала атрибуция блокчейн-аналитики. Если ни одно из условий не выбирает запись, ни одна не используется, и реквизиты считаются не указывающими ни на одну запись каталога.

Контакты

Поле contacts содержит адреса, на которые пишет канал EMAIL. Контакты с scope: platform обслуживают каждого клиента Didit: их отбирает Didit (source: staff), либо адрес на одном из собственных доменов VASP ответил на запрос по ссылке для ответа (source: learned). Контакты с scope: application доступны только вашему приложению: те, что добавили вы (source: application), и адрес за пределами собственных доменов VASP, который ответил на ваш запрос (source: learned). Перевод сначала отправляет письмо на контакт вашего приложения, затем на контакт платформы, который уже отвечал раньше (verified_at заполнен), и затем на остальные. Управляйте собственными контактами вашего приложения через API контактов контрагентов:
  • GET /v3/travel-rule/counterparty-contacts/ возвращает все контакты, на которые ваше приложение может отправлять письма, включая контакты платформы и контакты с ошибкой доставки, сначала новые. Разбивка на страницы выполняется через limit и offset. Фильтруйте через ?counterparty= и id записи каталога. Значение, которое не является UUID, возвращает 400.
  • POST добавляет приватный контакт (counterparty, email, необязательный name) и возвращает 201. Добавление адреса, который у записи уже есть среди ваших контактов, возвращает этот контакт. Неизвестная запись возвращает 400 ("No directory entry with this id.").
  • DELETE /v3/travel-rule/counterparty-contacts/{contact_uuid}/ удаляет один из ваших контактов (204). Контакты платформы удалить нельзя, для них возвращается 404.
Контакт, которому не было доставлено письмо или который пожаловался, помечается is_invalid: true, с invalid_reason (hard_bounce, soft_bounce, spam_complaint или suppressed), invalid_detail и invalid_at, и канал EMAIL пропускает его. Поле created_by пустое у контактов платформы и у контактов, которые добавил API-ключ.

Как записи попадают в каталог

  • Промежуточный реестр ESMA провайдеров услуг, связанных с криптоактивами, в соответствии с MiCA, который читается раз в неделю: название каждого провайдера, LEI и государство-член происхождения.
  • Публичный каталог TRISA, который читается раз в неделю: кто является каждым участником, его страна и его листинг в TRISA. Если листинг пропадает из каталога на три недели, он удаляется.
  • Глобальный реестр LEI дополняет запись LEI, добавляя другие юридические названия и страну. Он находит LEI записи по точному юридическому названию в её стране.
  • Атрибуция в блокчейн-аналитике добавляет или сопоставляет VASP, которому принадлежит адрес кошелька назначения, и сохраняет атрибутированное название как псевдоним.
  • Сотрудники Didit курируют записи: идентификаторы маршрутизации и сетевые листинги, которые они проверили, контакты для комплаенса, псевдонимы, бренды и их юридические лица, объединённые дубликаты и скрытые записи.
Данные, которые клиент сообщает о контрагенте, например входящий originating_vasp или Travel Address, остаются в переводе этого клиента и никогда не меняют общую запись. Записи сопоставляются сначала по LEI, затем по точному названию или псевдониму, и дополняются, а не дублируются.

Сеть Didit

Ваш профиль VASP является участником сети Didit, поэтому другие клиенты Didit находят его в каталоге и в выборе VASP у конечного пользователя, но только при выполнении всех условий ниже:
  • ваше приложение работает в продакшн-среде (не в песочнице), и Travel Rule включён;
  • is_discoverable равно true, а legal_name заполнено;
  • compliance_email заполнен и подтверждён: отправьте подтверждение запросом POST /v3/travel-rule/settings/compliance-email/confirmation/ и откройте ссылку из письма. При смене адреса нужно новое подтверждение;
  • Didit не отметил приложение как тестовое, демонстрационное или внутреннее.
Канал INTERNAL сам доставляет на проверенный вами кошелёк, как описано в разделе Отправка перевода: для него нужны только продакшн-среда приложения, включённый Travel Rule и is_discoverable, а не весь список выше. GET /v3/travel-rule/settings/ возвращает didit_network с полями listed и compliance_email_confirmed. В missing перечислено каждое условие, которое ещё не выполнено: travel_rule_off, sandbox_application, not_discoverable, legal_name_missing, compliance_email_missing, compliance_email_unconfirmed, excluded_by_staff, internal_organization.

Идентификаторы маршрутизации

Исходящая маршрутизация обращается к записи каталога каждый раз, когда ваш перевод называет контрагента через counterparty_vasp_lei или counterparty_vasp_name (работает и псевдоним). Запись содержит один идентификатор для каждой сети, в которой до контрагента можно дойти, проверенную конечную точку TRP и его контакты для комплаенса. В routes показано, какими из них может пользоваться ваше приложение. Вы всегда можете обойти каталог для одного перевода, передав явные поля из раздела Поля для определения контрагента.

Проверка должной осмотрительности контрагента

Прежде чем средства ваших клиентов поступят к другому VASP, нужно знать, кто это VASP и принимаете ли вы его. Проверка контрагента состоит из двух уровней:
  • Общая оценка. Каждая запись справочника содержит оценку должной осмотрительности, уровень риска, статус отзывчивости и отчёт о базовых проверках. Didit вычисляет их на основе собственных данных платформы, поэтому все клиенты видят одни и те же значения.
  • Ваши собственные записи. Ваше решение о VASP, ваш скрининг контрагента на санкции и негативные публикации в СМИ, а также анкеты должной осмотрительности, которыми вы обменялись с ним, принадлежат только вашему приложению. Другие клиенты их не видят, и они не меняют общую запись.
Ваши собственные записи работают одинаково как для записи справочника, так и для участника сети Didit: обращайтесь к ним по id, который показывает справочник.

Оценка и уровень риска

dd_score принимает значения от 0 до 100, и чем выше, тем безопаснее. Каждый результат поясняет это в dd_score_semantic, которое всегда равно higher_is_safer. risk_level определяется диапазоном, в который попадает оценка: Оценка складывается из семи компонентов. dd_score_breakdown показывает каждый из них в виде {points, max, reason, detail}: max задаёт вес компонента, reason является машиночитаемым кодом, который можно перевести, а detail содержит пояснение на английском в одну строку. Баллы округляются до одного знака после запятой, а итог до целого числа. history учитывает те же запросы, что и отзывчивость, без временного окна: обмен со статусом COMPLETED или FINISHED считается успешным, а обмен со статусом EXPIRED, COUNTERPARTY_MISMATCHED_DATA или COUNTERPARTY_VASP_GENERAL_DECLINE считается неуспешным. Отмена является действием самого отправителя, и она никогда не учитывается. Didit пересчитывает оценку, когда обмен с записью завершается, финализируется, истекает, для него выявляется несоответствие данных или он отклоняется, когда запись обновляется или курируется, а также для каждой записи при еженедельном обновлении справочника. dd_assessed_at показывает, когда расчёт выполнялся в последний раз. У записи, которую ещё не оценивали, нет dd_assessed_at, dd_score равен 0, а risk_level пуст, поэтому ориентируйтесь на risk_level, а не на сырой балл. Участники сети Didit не оцениваются: их dd_score равен null, risk_level пуст, а dd_score_breakdown пуст.

Базовые проверки

essential_checks представляет собой краткий отчёт о четырёх проверках, которые Didit выполняет для каждой записи справочника, и только по проверенным источникам платформы, а никогда по данным, переданным клиентом: Каждая проверка в checks сообщает свой status (pass, fail или unknown, если ни один источник в записи не позволяет определить её результат), машиночитаемый reason и detail на английском в одну строку. passed, failed, unknown и total подсчитывают проверки, level равен essential, а checked_at показывает, когда был сформирован отчёт. Компонент essentials оценивает долю пройденных проверок среди проверок с определённым результатом, поэтому проверка со статусом unknown не учитывается ни в пользу записи, ни против неё. Отчёт пуст, пока еженедельное обновление справочника впервые не проверит запись, и всегда пуст для участника сети Didit.

Отзывчивость

responsiveness показывает, отвечает ли VASP на запросы Travel Rule:
total подсчитывает запросы, которые клиенты Didit направили VASP за последние 90 дней и результат которых известен, а answered те из них, на которые VASP ответил:
  • Учитываются: запросы, отправленные по сетевому каналу или каналу протокола, и запросы, отправленные по электронной почте на общий адрес, то есть на адрес комплаенса записи или на контакт платформы.
  • Никогда не учитываются: запрос, отправленный на адрес электронной почты, который ввёл один клиент, или на личный контакт клиента. Такой обмен остаётся в истории переводов этого клиента и никогда не присваивает VASP метку для всех остальных.
  • Ответ получен: COMPLETED, FINISHED, COUNTERPARTY_VASP_GENERAL_DECLINE или COUNTERPARTY_MISMATCHED_DATA. Отказ или несоответствие данных тоже считаются ответом: VASP вступил во взаимодействие.
  • Ответа нет: EXPIRED.
  • Ни то ни другое: любой другой статус. Запрос со статусом CANCELLED является действием самого отправителя, COUNTERPARTY_VASP_NOT_REACHABLE может быть сбоем на стороне отправителя, а запрос, который ещё выполняется, пока не имеет результата.
Участники сети Didit отвечают внутри Didit и всегда показывают UNKNOWN с нулевыми счётчиками. Для неотвечающего контрагента действуйте по политике неответов или по пресету travel-rule-counterparty-unresponsive.

Ваше решение по контрагенту

Зафиксируйте вердикт вашей команды комплаенса по контрагенту с помощью PUT /v3/travel-rule/vasps/{vasp_id}/decision/:
  • Решение видно только в вашем приложении: другой клиент его не видит, и оно никогда не меняет общую запись.
  • Само по себе решение ничего не меняет и никогда не влияет на маршрутизацию перевода. Оно попадает в ваши транзакции через поле правила counterparty_dd_status: установите пресеты или напишите собственное правило на его основе.
  • Решение по бренду распространяется на каждую из его сущностей, у которой нет собственного решения, а в inherited_from сущность показывает бренд. Решение по сущности переопределяет решение по бренду для этой сущности.
  • GET возвращает действующее решение или {"decision": null}. DELETE удаляет ваше собственное решение и возвращает то, что действует после этого: решение по бренду для сущности, а иначе null. Он отвечает 404, если у вашего приложения нет собственного решения по контрагенту.
  • reason необязательно, до 255 символов, и каждый PUT заменяет его. decided_by указывает пользователя консоли, который принял или последним изменил решение; поле пустое, если решение принял API-ключ.

Скрининг контрагента

Скрининг контрагента проверяет саму компанию VASP на наличие санкций и негативных публикаций в СМИ. Он включается явно и тарифицируется: включите его параметром counterparty_screening_enabled в настройках. Пока он выключен, проверки не выполняются и не тарифицируются.
  • Оплата. Каждый новый скрининг стоит цену Company AML Screening для вашей организации. GET /v3/travel-rule/vasps/{vasp_id}/screening/ возвращает эту price, fresh_days и то, включён ли скрининг (enabled), без оплаты. Скрининг моложе 30 дней считается актуальным: POST возвращает его снова с 200 и outcome: reused и ничего не списывает, если только вы не отправите refresh: true. Новый скрининг отвечает 201 с outcome: screened. Приложения в песочнице никогда не проверяются и не тарифицируются.
  • Автоматический скрининг. Если скрининг включён, Didit также проверяет контрагента, которому направляется исходящий обмен, в момент начала обмена, до того как ваши правила оценят транзакцию, с тем же повторным использованием и оплатой. Отказ или сбой на этом этапе никогда не приводит к провалу перевода.
  • Результат. status равен Approved, если возможные совпадения не требуют внимания, и In Review или Declined при возможном совпадении по санкциям или негативным публикациям в СМИ. flagged равен true, пока актуальный скрининг имеет статус In Review или Declined.
  • Влияние. Пока ваш последний скрининг контрагента помечен и скрининг остаётся включённым, counterparty_risk_level в ваших правилах равен HIGH, поэтому пресет travel-rule-counterparty-high-risk, после установки, удерживает его переводы. Общий risk_level в справочнике не меняется.
  • Конфиденциальность. Скрининг виден только в вашем приложении: другой клиент его не видит, и он никогда не меняет общую запись. Справочник показывает ваш последний скрининг как entity_screening.
  • Отказы. 409, если скрининг выключен, для приложения в песочнице или пока уже выполняется скрининг того же контрагента. 403 с code: insufficient_credits, если баланса не хватает на цену. 502, если скрининг не удалось завершить. Ни один из этих ответов ничего не списывает.

Анкета комплексной проверки

Запросите у контрагента ответы на вопросы комплексной проверки с помощью стандартной анкеты Didit. POST /v3/travel-rule/vasps/{vasp_id}/questionnaires/ отправляет её на адрес комплаенса, записанный для контрагента, вместе с одноразовой ссылкой на страницу ответов:
  • Куда отправляется. sent_to должен быть адресом комплаенса, записанным в системе: для записи справочника это адрес комплаенса или контакт, который ваше приложение видит в contacts; для участника сети Didit это его подтверждённая почта комплаенса. Если не указывать поле, используется адрес, на который уходит письмо Travel Rule контрагенту: сначала ваш собственный контакт, затем контакт платформы, затем адрес комплаенса записи. Didit никогда не отправляет анкету никуда больше и отвечает 400, если у контрагента нет адреса комплаенса в системе.
  • Ответ. Ссылка открывает /v3/travel-rule/questionnaires/{token}/, страницу для представителя контрагента, которому не нужны аккаунт или API-ключ. Представитель отвечает на каждый вопрос, до 2 000 символов на каждый (N/A, если вопрос неприменим), и отправляет анкету один раз. Контрагент также может отправить {"answers": {"<question id>": "<answer>"}} на ту же ссылку. Ссылка, которая уже использована, заменена или просрочена, вместо этого показывает страницу истечения срока. Письмо и страница переведены; сами вопросы всегда на английском, поэтому их смысл не меняется между языками.
  • Сроки. Контрагенту дается 30 дней на ответ (expires_at). Ответы остаются актуальными 365 дней (valid_until), и до этого момента current равно true. Неотвеченная анкета после срока и ответы после окончания срока действия получают статус EXPIRED.
  • Одна открытая анкета на контрагента. Новая отправка заменяет вашу более раннюю неотвеченную анкету тому же контрагенту: она получает статус EXPIRED, а её ссылка перестаёт работать. На один адрес в минуту можно отправить одно письмо; более частая отправка возвращает 429.
  • Чтение ответов. GET /v3/travel-rule/vasps/{vasp_id}/questionnaires/ возвращает ваши анкеты для контрагента, сначала самые новые, с answers, где ключом служит id вопроса, после ответа. Справочник показывает их как questionnaires. Они видны только в вашем приложении.
Форма версии 1 содержит следующие двенадцать вопросов:

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

GET /v3/travel-rule/transfers/?counterparty={vasp_id} выводит переводы по Travel Rule вашего приложения с одним контрагентом, от новых к старым, по id, который показывает справочник: ваши переводы, контрагентом которых является эта запись справочника, или ваши обмены INTERNAL с этим участником сети Didit. Значение, которое не является допустимым id, возвращает 400. Каждая строка содержит reason сразу после status: reason самого перевода. Так список позволяет отличить письмо комплаенса, которое не было доставлено, от контрагента, до которого нет маршрута, не открывая каждый перевод.

Политики переходного периода и контрагентов, не отвечающих на запросы

Когда срок перевода истекает (прошло confirmation_timeout_hours, пока он находился в статусе AWAITING_COUNTERPARTY), timeout_outcome определяет, что происходит с его транзакцией. Две настройки позволяют по-разному обрабатывать две распространённые причины тайм-аута: Обе настройки принимают HOLD, REJECT или PROCEED с тем же значением, что и для timeout_outcome, либо пустую строку, чтобы следовать timeout_outcome. Обе пустые по умолчанию, поэтому ничего не меняется, пока вы не зададите одну из них:
Первая из них, которая применима, определяет результат:
  1. sunrise_policy, если она задана и юрисдикция контрагента входит в sunrise_jurisdictions.
  2. non_responsive_policy, если она задана и контрагент имеет статус UNRESPONSIVE.
  3. timeout_outcome во всех остальных случаях, включая перевод участнику сети Didit или контрагенту без записи в справочнике.
sunrise_jurisdictions доступен только для чтения и сейчас содержит ["CO", "KH", "VN"]. Только эти страны считаются находящимися в переходном периоде: для любой другой, а также для пустой или неизвестной юрисдикции используется timeout_outcome, поэтому контрагент никогда случайно не получит вашу политику переходного периода. В хронологии истёкшего перевода указывается результат, который был применён, например sunrise_policy=PROCEED (counterparty jurisdiction CO does not enforce the Travel Rule yet). Ваши правила продолжают выполняться после истечения срока, поэтому такое правило, как EU TFR tr_eu_expired_review, по-прежнему может перевести транзакцию на проверку при PROCEED.

Поля правил и пресеты

Четыре поля правила содержат то, что ваше приложение знает о контрагенте, до которого дошёл обмен Travel Rule по транзакции: его запись в справочнике или, для обмена INTERNAL, клиента Didit, который его получил. Каждое поле пустое, если обмен не дошёл до известного контрагента. Пять пресетов в наборе travel_rule работают с этими полями. Установите их в Transactions → Rules → Library в Business Console или с помощью API библиотеки правил: Все пять применяются только к транзакциям Travel Rule.

Webhooks

Настройте адрес назначения webhook для подписки на travel_rule.status.updated, чтобы получать уведомление о каждом переходе статуса каждого исходящего и входящего перевода в вашем приложении, включая части переводов на канале INTERNAL. Маршрутизация выполняется после того, как ответ на создание транзакции уже возвращён, поэтому такие исходы, как COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE, или обмен на INTERNAL, который сразу завершается, приходят к вам через это событие, а не в ответе на создание.
Поле rail остаётся пустым, пока канал не выбран. Для одной транзакции Didit отправляет travel_rule.status.updated после transaction.created: когда Didit создаёт транзакцию (ваш POST /v3/transactions/, регистрация sunrise, входящий запрос TRP или входящая часть перевода на канале INTERNAL), он сначала отправляет transaction.created. Каждое событие доставляется и повторяется отдельно, поэтому transaction.created, которому потребовалась повторная попытка, может прийти после события статуса. Опирайтесь в обработчике на transaction_id и читайте транзакцию, когда событие статуса указывает на транзакцию, которую вы ещё не видели. Перевод, отправленный через TRUST, не получает события COMPLETED: TRUST не возвращает ничего, что Didit может прочитать, поэтому, если отправка не завершилась ошибкой или вы её не отменили, последним событием будет EXPIRED. Для GTR, VERIFYVASP и SYGNA событие следует за ответом, который Didit получает из сети. Приложения в песочнице также отправляют это событие, с "environment": "sandbox". Перевод, на который ответил Didit Test VASP, содержит rail: "TEST_VASP", test_vasp: true и test_vasp_scenario. Полный каталог событий, проверку подписи и политику повторных попыток см. в руководстве по webhook.

Цены

Сбор за Travel Rule составляет $0.02 за каждый исходящий обмен, который начинается, и списывается однократно при создании транзакции, независимо от того, сколько переходов статуса обмен проходит потом. Повторная отправка (resend) никогда не списывает его снова.
  • Он применяется к исходящей транзакции travelRule в приложении с включённым Travel Rule, сумма которой равна или превышает ваш threshold_amount, поэтому Didit запускает для неё управляемый обмен. Перевод на некастодиальный кошелёк или на собственный верифицированный кошелёк пользователя также подпадает под это правило: доказательство владения и есть его обмен.
  • Каждая другая исходящая транзакция, которая содержит данные Travel Rule, тарифицируется как стандартный Transaction Screening. Ваш тариф включает его до месячного лимита транзакций, а сверх лимита он стоит $0.001: это перевод ниже вашего порога, платёж Lightning, к которому Travel Rule не применяется, и перевод, зафиксированный в пассивном режиме, когда Travel Rule выключен. Транзакция тарифицируется по одной из двух строк, никогда по обеим.
  • Входящие транзакции travelRule не несут сбора за Travel Rule или мониторинг: это входящая часть перевода, которую Didit создаёт в приложении получателя на канале INTERNAL, входящие запросы TRP и регистрации после депозита.
  • Приложение, которое подключилось к Travel Rule бесплатно, не платит ни один из сборов за исходящие транзакции travelRule, пока не включит биллинг исходящих через POST /v3/travel-rule/workspace/billing/. Приём остаётся бесплатным.
  • Приложения в песочнице никогда не тарифицируются.
  • Сбор за Travel Rule по прайсу составляет $0.02, а Transaction Screening стоит $0.001 сверх вашего лимита. Каждая транзакция оплачивается по цене той строки, по которой она тарифицируется.
  • Если крипто-AML или скрининг кошельков также выполняется для той же транзакции (например, входящий депозит с currency_kind: "crypto" и включённым мониторингом), он тарифицируется отдельно по стандартной ставке Wallet Verification & Screening. В сбор за Travel Rule он не входит.
  • Скрининг контрагента включается по выбору и тарифицируется отдельно, по цене Company AML Screening за каждую проверку. Проверка, выполненная менее 30 дней назад, используется повторно без оплаты. Решения, анкеты и оценка должной осмотрительности в каталоге ничего не стоят.

Цены на атрибуцию

Атрибуция через блокчейн-аналитику требует одного запроса по кошельку к провайдеру аналитики, и вы всегда получаете полный результат этого запроса:
  • Крипто-мониторинг уже проверил кошелёк в этой транзакции - сохранённый результат используется повторно. Один вызов аналитики и одно списание Wallet Verification & Screening (то, которое уже выполнил крипто-мониторинг). Атрибуция ничего не добавляет.
  • Крипто-мониторинг не запускался - Didit делает необходимый вызов аналитики один раз и передаёт его полный результат дальше. Транзакция оплачивается по стандартной ставке Wallet Verification & Screening ($0.15 при аналитике, управляемой Didit) и получает полный вывод крипто-скрининга: оценку риска, категорию риска кошелька, снимок сети, а не только название VASP. Строка использования помечается триггером travel_rule_attribution, чтобы списание можно было объяснить.
  • Собственный ключ аналитики - здесь применяется та же платформенная комиссия, что и для крипто-мониторинга с вашим ключом ($0.02 за вызов). Ваш провайдер выставляет счёт за базовый запрос напрямую вам.
  • Неуспешные запросы никогда не тарифицируются. Повторные отправки переиспользуют записанную атрибуцию без нового вызова и списания, а приложения в песочнице никогда не тарифицируются.
Итак, исходящий перевод, которому понадобилась атрибуция, стоит $0.02 (Travel Rule) плюс одно списание Wallet Verification & Screening. Когда включены оба продукта, вызов аналитики и его списание происходят только один раз.

Пакет правил EU TFR

В пакет travel_rule_eu_tfr входят четыре предустановленных правила, созданных специально для обязательств по EU TFR. Как и любой другой предустановленный пакет, включите его в Business Console в разделе Transactions → Rules → Library. Правила срабатывают при каждом соответствующем переходе: при создании перевода, при подтверждении владения, при ответе на получение по электронной почте и из процесса проверки истёкших переводов. Поэтому перевод, который так и не получил окончательный статус, всё равно попадает на проверку комплаенса, а не завершается незаметно.

Почему это работает лучше на одной платформе

Соответствие Travel Rule по сути является задачей идентификации в платёжной оболочке, и Didit уже располагает этими данными:
  • Проверенные данные уже у вас. Субъект каждого перевода связан через vendor_data с KYC-профилем, который Didit верифицировал при онбординге (OCR документа, проверка живости, сравнение лиц). Поэтому вы можете формировать originator_data из проверенных полей на той же платформе. Для пользователя с одобренной KYC-верификацией Didit передаёт проверенную идентичность, а не значения, которые вы ввели вручную (см. Проверенные данные и их происхождение).
  • Биометрическая проверка для рискованных переводов. Правило может удерживать любой перевод на крупную сумму до тех пор, пока пользователь не пройдёт повторную биометрическую проверку по лицу, снятому при онбординге. Это самый надёжный ответ на захват учётной записи в момент вывода средств. См. пример с разбором.
  • Один граф устройств. Переводы, отправленные через SDK, содержат те же данные об устройстве, что и KYC-сессии, поэтому правило «новое устройство + крупная сумма + первый получатель» пишется одним правилом, а не соединением таблиц в хранилище данных.
  • Скрининг, правила, кейсы и обмен в одном месте. Скрининг кошелька, который определяет VASP контрагента, это тот же скрининг, который видят ваши правила и аналитики: одно списание, одна консоль, один журнал аудита.
  • Опубликованные цены. $0.02 за исходящий обмен, без тарифов, зависящих от объёма, и без звонка в отдел продаж.

FAQ

Как работает обмен

Didit проводит обмен сам. Когда вы отправляете перевод travelRule, Didit выполняет поиск контрагента в реальном времени, передаёт данные отправителя и получателя принимающей VASP по соответствующим каналам и присваивает переводу статус COMPLETED только после того, как сторона контрагента подтвердит это - это управляемый обмен подтверждениями, а не журнал после расчёта. Ответ контрагента обрабатывается по всем каналам, кроме TRUST, который только отправляет, поэтому перевод по нему истекает, а не завершается. По GTR, VERIFYVASP и SYGNA Didit получает ответ из ответа сети или опрашивает её каждые 5 минут, и ни один обмен по этим каналам пока не был завершён в работающей сети. По TRP, если контрагент не отвечает сразу, нужен публичный хост TRP Didit, который пока не запущен (см. Сети Travel Rule). Didit также хранит полную запись и статус соответствия каждого обмена (жизненный цикл из 13 статусов, webhook и журнал аудита). Если вы предпочитаете вести Travel Rule в собственном процессе, Didit может вместо этого записывать данные пассивно как вашу единственную систему учёта (см. вопрос о пассивной записи ниже). Доступны оба режима: активный обмен запускается, когда вы задаёте is_enabled: true в настройках (по умолчанию false). До этого Didit записывает данные пассивно.
Обмен нативный. Didit использует собственную реализацию Travel Rule Protocol (TRP), которая будет доступна по адресу trp.didit.me после запуска публичного хоста (пока он не отвечает, контрагенты не могут связаться с вами по TRP), и напрямую реализует сетевые протоколы отрасли: GTR (Global Travel Rule), TRUST, VerifyVASP и Sygna Bridge (поддерживается нативно, включая зашифрованный ECIES формат IVMS 101 от Sygna и подписанные запросы на разрешение). Didit не является перепродавцом одного стороннего уровня взаимодействия. Связь с контрагентом, который работает в конкретной сети членства, обязательно проходит через эту сеть, потому что в Travel Rule нет единого универсального канала. Однако поиск, обмен подтверждениями, проверку и ведение записей выполняет Didit. Сегодня для каждого сетевого канала нужно собственное членство, а TRUST только отправляет: см. Обзор.
Реализованы нативно и используются в строгом порядке приоритета: INTERNAL (обе VASP на Didit), TRP (OpenVASP Travel Rule Protocol), GTR, TRUST, VerifyVASP, Sygna Bridge и резервный канал EMAIL для контрагентов без инструментов. Сетевым каналам сегодня нужно ваше собственное членство, TRUST только отправляет, а приём через сеть пока не включён: см. Сети Travel Rule. CODE, TRISA, Notabene и Veriscope появляются в Marketplace Didit как плитки запроса доступа, интеграции с ними пока нет. Полную таблицу маршрутизации см. в разделе Как Didit маршрутизирует обмен, а о том, до кого каждая сеть может дотянуться сегодня, см. в разделе Сети Travel Rule.
TRP - базовый вариант, который мы рекомендуем: для него не нужно общее членство. Если вы делитесь своим Travel Address, с вами может связаться любая VASP, которая поддерживает TRP. Когда контрагент известен только по адресу кошелька, атрибуция по блокчейн-аналитике направляет его по TRP только в том случае, если у записи каталога с атрибуцией уже есть конечная точка TRP. При исходящих переводах Didit отправляет по этому каналу активы из списка идентификаторов активов TRP. Если контрагент не отвечает сразу, нужен публичный хост TRP Didit, который пока не запущен. Для сетей членства выбирайте по тому, где работают ваши контрагенты: GTR обеспечивает широкое глобальное покрытие, TRUST ориентирован на VASP из США, а VerifyVASP (который теперь также управляет Sygna Bridge) имеет широкое покрытие в Азии. Да, сегодня вам нужно собственное членство: у Didit пока нет членства платформы в этих сетях, и мы подключаемся как провайдер. Подключите своё членство в Marketplace. Вы не привязаны к одной сети. Помните, что TRUST только отправляет, поэтому перевод по нему не завершается, а ни один обмен по сети пока не был завершён в работающей сети.
С помощью цепочки определения контрагента: другой клиент Didit, который подтвердил контроль над кошельком (канал INTERNAL), затем ваша адресная книга кошельков с сохранённым Travel Address, затем протокольные и сетевые каналы и, наконец, атрибуция по блокчейн-аналитике. Если перевод содержит только адрес кошелька назначения, Didit сверяет его с данными блокчейн-аналитики, определяет биржу или кастодиальный сервис контрагента и заносит его в каталог VASP. Перевод автоматически маршрутизируется только в том случае, если у записи каталога уже есть маршрут, например конечная точка TRP. Иначе он завершается статусом COUNTERPARTY_VASP_NOT_FOUND с сохранённым именем контрагента. Вы можете отправить его повторно, когда появится маршрут, например после того, как сохраните travel_address контрагента в записи адресной книги для этого кошелька (для активов из списка идентификаторов активов TRP).

Цены и лицензирование

Travel Rule - отдельный продукт с оплатой по использованию. Она не входит в верификацию личности. Стоимость составляет **$0.02 за каждый начатый исходящий обмен** ([опубликованные цены](#цены), без уровней, зависящих от объёма, и без звонка с отделом продаж). Если для определения VASP контрагента маршрутизации нужен запрос к блокчейн-аналитике, этот запрос оплачивается один раз по [ставке атрибуции](#цены-на-атрибуцию): от $0.15 при управляемом доступе или $0.02 при наличии собственного ключа провайдера. Повторно он не оплачивается, если мониторинг криптовалют уже проверил тот же кошелёк. Перевод ниже вашего порога или перевод, записанный при выключенной Travel Rule, оплачивается как проверка транзакций. Входящие переводы, например части переводов, которые Didit создаёт на стороне получателя, не облагаются платой за Travel Rule.
Нет. Транзакция travelRule оплачивается одной строкой: платой за Travel Rule ($0.02), когда она начинает исходящий обмен, и стандартной проверкой транзакций в остальных случаях. Запрос к блокчейн-аналитике оплачивается один раз. Если мониторинг криптовалют уже проверил кошелёк назначения в этой транзакции, атрибуция использует сохранённый результат без дополнительной оплаты. Входящие части переводов, которые Didit создаёт на стороне получателя, никогда не оплачиваются платой за Travel Rule.
Да. Возможности Didit в области KYT и мониторинга транзакций, AML и санкционного скрининга, оценки риска кошельков по данным блокчейн-аналитики и Travel Rule опубликованы на docs.didit.me, и вы можете ссылаться на эти страницы в регуляторном досье. Для официального описания продукта, подготовленного под конкретную заявку, например заявку на лицензирование в AIFC/AFSA, заявку в соответствии с MiCA или другую заявку на лицензирование VASP, обратитесь к своей команде Didit и укажите формат, который требует ваш регулятор.

Операции и данные

Они сразу получают статус NOT_APPLICABLE: канал не задействуется, и данные об идентификации не покидают Didit. Транзакция тарифицируется как обычный Transaction Screening, а не по тарифу Travel Rule. Didit сравнивает порог с суммой, пересчитанной в валюту приложения по умолчанию. Если Didit не может пересчитать сумму, порог не применяется, и перевод обрабатывается как обмен данными по Travel Rule. У регламента EU TFR нет минимальной суммы, поэтому CASP из ЕС должны оставлять threshold_amount равным 0.00.
Перевод находится в состоянии AWAITING_COUNTERPARTY, пока не истечёт confirmation_timeout_hours. Затем планировщик переводит его в EXPIRED и применяет ваш timeout_outcome к базовой транзакции: HOLD для проверки, REJECT для отклонения или PROCEED для оставления без изменений. Предустановленное правило tr_eu_expired_review дополнительно выводит каждое истечение срока для ваших аналитиков. Ситуация, когда контрагент вообще не принимает сообщение, другая: Didit повторяет попытку, а после шестой неудачной попытки перевод переходит в COUNTERPARTY_VASP_NOT_REACHABLE (см. Доставка и повторные попытки).
Да, для этого есть канал EMAIL. Контактное лицо контрагента по комплаенсу получает одноразовую ссылку на получение и может подтвердить или отклонить обмен на размещённой странице без аккаунта Didit, без API-ключа и без интеграции. Для депозитов, которые были зачислены до того, как обмен стал возможен, используйте регистрацию sunrise после депозита.
Укажите адрес назначения как некастодиальный кошелёк (payment_method.method_type: "unhosted_wallet"). Didit пропускает маршрутизацию через VASP и вместо этого собирает подтверждение того, что ваш клиент контролирует кошелёк: подписание сообщения, тест Satoshi, скриншот или самодекларацию, через виджет подтверждения владения кошельком. Это требование EU TFR к некастодиальным кошелькам в виде API.
Да. Пока владение кошельком не подтверждено на вашей стороне, персональные данные контрагента во входящем переводе маскируются как в объекте перевода, так и в данных стороны контрагента в транзакции (см. Маскированные данные контрагента). Страницы получения по email показывают только сводку перевода (название VASP отправителя, сумму, адрес назначения кошелька), но никогда не показывают данные физического лица. Обмен по каналам выполняется по HTTPS, а сетевые каналы (GTR, TRUST, VERIFYVASP, SYGNA) дополнительно шифруют payload IVMS открытым ключом принимающего VASP.
Да. Если Travel Rule отключено, любые travel_rule_details, которые вы передаёте (включая переданный вами status, текущий или устаревший), записываются в транзакцию пассивно: рельс не запускается, ничего не согласовывается. Используйте это, чтобы внести данные задним числом или вести собственный процесс, сохраняя одну основную систему учёта.
Используйте приложение в песочнице с включённым Travel Rule. В песочнице правила и биллинг не применяются, а вебхуки transaction.created и transaction.status.updated не отправляются, и каждый исходящий обмен направляется в Didit Test VASP по каналу TEST_VASP. Тестовый VASP отвечает внутри Didit по выбранному вами сценарию: принятие, отклонение, несовпадение имени, недоступность, тайм-аут или некастодиальный кошелёк. Вы получаете вебхуки travel_rule.status.updated с пометкой environment: sandbox, и ни одно сообщение не доходит до реального контрагента. Для входящих переводов попросите тестовый VASP отправить вам перевод с помощью Trigger Test VASP Inbound Transfer или зарегистрируйте его через POST /v3/travel-rule/inbound/. В обоих случаях решение принимается на основе адресной книги ваших кошельков: ON_HOLD, UNCONFIRMED_OWNERSHIP, COUNTERPARTY_MISMATCHED_DATA либо COMPLETED, а затем FINISHED. Для двустороннего обмена с другим реальным приложением используйте два своих продакшн-приложения по каналу INTERNAL: принимающее приложение должно быть доступно для обнаружения другими приложениями и доказать контроль над адресом назначения кошелька подписанным сообщением или тестовым депозитом.

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

Виджет подтверждения владения кошельком

Подтвердите, что клиент контролирует кошелёк: подписью, тестом Satoshi, скриншотом или самодекларацией.

Интероперабельность

Эндпоинты для контрагентов, которые другие VASP используют, чтобы связаться с вами: TRP, колбэки, получение по email, sunrise.

Сети

Что каждый рельс и каждая сеть поддерживают сейчас и что ещё ждёт членства.

Тестирование в песочнице

Проверьте каждый исход на Didit Test VASP перед запуском в продакшн.

Отправка транзакций

Как добавить данные Travel Rule в payload Create Transaction.

Библиотека правил

Полный каталог предустановленных правил, включая пакет Travel Rule.

Вебхуки

Подпишитесь на travel_rule.status.updated и на все остальные семейства событий.

Руководство по консоли

Просмотр статуса Travel Rule в детальном представлении транзакции.
Последнее изменение 7 ноября 2026 г.