Passer au contenu principal
Didit assure l’échange d’informations entre donneur d’ordre et bénéficiaire prévu par le règlement sur le transfert de fonds (TFR) du GAFI et de l’UE pour les transferts d’actifs virtuels. Il recherche un canal qui atteint le VASP de la contrepartie, y négocie les données requises, suit l’échange au cours d’un cycle de vie à 13 états, et vous indique quand l’échange est terminé afin que vous puissiez libérer les fonds on-chain.

Vue d’ensemble

La règle de voyage du GAFI (Recommandation 16), mise en œuvre dans l’UE sous le nom de règlement sur les transferts de fonds (TFR), oblige le prestataire de services sur actifs virtuels (VASP) émetteur, appelé VASP du donneur d’ordre, à collecter et transmettre les informations sur le donneur d’ordre et le bénéficiaire au VASP récepteur, appelé VASP du bénéficiaire, avant que le transfert d’actifs virtuels ne soit réglé. Contrairement à la plupart des juridictions, le TFR de l’UE s’applique sans montant minimum : chaque transfert de cryptoactifs entre dans son champ, et pas seulement les transferts au-dessus d’un seuil de déclaration. Le produit Travel Rule de Didit est un échange géré, et non un simple champ de données :
  • Vous soumettez un transfert avec transaction_category: "travelRule".
  • Didit essaie les canaux dans l’ordre de priorité pour atteindre le VASP de la contrepartie, exécute l’échange sur le premier qui l’atteint et, lorsque l’autre partie renvoie un nom de bénéficiaire, le compare à celui que vous avez envoyé.
  • Ce n’est qu’une fois que l’échange atteint COMPLETED que vous devez diffuser le transfert on-chain.
  • Vous renvoyez ensuite le hash de la transaction on-chain à Didit pour clôturer l’échange avec le statut FINISHED.
Sept canaux sont livrés dans cette version, essayés dans un ordre de priorité strict : le premier capable d’atteindre la contrepartie l’emporte : Les canaux réseau (GTR, TRUST, VERIFYVASP, SYGNA) nécessitent une adhésion au réseau côté émetteur, et aujourd’hui cette adhésion doit être la vôtre. Connectez vos identifiants d’adhésion dans la Marketplace de la console (catégorie Travel Rule networks), et vos transferts passent alors sous votre propre identité réseau. Didit ne détient aujourd’hui aucune adhésion de plateforme à l’un des quatre réseaux, et est en cours d’onboarding en tant que prestataire auprès d’eux. Tant que cet onboarding n’est pas terminé, platform_available vaut false pour les quatre, le mode didit des paramètres (routage via l’adhésion de plateforme de Didit) n’est disponible pour aucun réseau, et disabled_networks n’a rien à désactiver. Une adhésion que vous connectez vous-même a toujours la priorité sur une adhésion de plateforme. Aucune adhésion réseau n’est incluse par défaut, GTR compris. Activer la Travel Rule n’active ni GTR, ni TRUST, ni VerifyVASP, ni Sygna. Consultez Get Travel Rule Settings pour les modes networks effectifs de votre application, et Travel Rule networks pour connaître les contreparties que chaque réseau peut atteindre aujourd’hui. Si les quatre renvoient unavailable, aucun ne peut router un échange : connectez votre propre adhésion dans la Marketplace, ou utilisez une contrepartie joignable via INTERNAL, TRP ou EMAIL. Lors de l’enregistrement d’une adhésion, son code de participant doit être le vôtre : l’enregistrement renvoie 400 lorsque le code est le code propre de Didit sur ce réseau, ou un code déjà détenu par l’adhésion d’une autre application. Une adhésion GTR nécessite sa clé privée ed25519 (en base64), celle dont la clé publique est enregistrée dans le portail de GTR : elle chiffre les données personnelles que vous envoyez et déchiffre celles que vous recevez. Une adhésion VerifyVASP pointe vers votre propre Enclave VerifyVASP : son URL est celle de votre Enclave, et son token est celui que votre Enclave présente à Didit. Même avec votre propre adhésion, un canal réseau ne route un transfert que si son beneficiary_data contient l’identifiant de la contrepartie sur ce réseau (counterparty_gtr_vasp_code, counterparty_trust_membership_id, counterparty_verifyvasp_id ou counterparty_sygna_vasp_code), ou si l’entrée d’annuaire de la contrepartie le répertorie sur ce réseau (voir Routes). platform_available indique si l’adhésion de plateforme est configurée, et non un contrôle de santé du réseau en direct. De même, connected signifie que vos identifiants d’adhésion sont enregistrés et ont passé la validation locale ; le premier échange en direct les vérifie auprès du réseau. Un mode didit ou own ne garantit ni l’accessibilité de la contrepartie ni la réussite de la livraison. Avec votre propre adhésion, Didit lit la réponse de la contrepartie via GTR, VERIFYVASP et SYGNA à partir de la réponse du réseau ou en interrogeant le réseau toutes les 5 minutes, ce qui finalise ou refuse le transfert. TRUST ne fait qu’envoyer : un transfert envoyé par ce biais reste en AWAITING_COUNTERPARTY jusqu’à l’écoulement de confirmation_timeout_hours, puis passe en EXPIRED et votre timeout_outcome s’applique. La réception d’un transfert qu’un autre VASP vous envoie par l’intermédiaire d’un réseau n’est pas encore activée, et aucun échange par l’intermédiaire d’un réseau n’a encore été finalisé sur un réseau en production : voir Réseaux Travel Rule. D’autres réseaux - CODE (l’alliance coréenne), TRISA, Notabene et Veriscope - sont listés dans la Marketplace de la console sous forme de vignettes de demande d’accès. Didit n’a pas encore d’intégration avec eux, si bien qu’aucun transfert ne peut être acheminé par leur intermédiaire aujourd’hui. Lorsqu’aucun des sept canaux ne parvient à identifier une contrepartie, Didit effectue une étape de résolution supplémentaire avant d’abandonner : l’attribution par analyse de la blockchain. Le portefeuille de destination est recherché dans les données d’analyse de la blockchain ; lorsqu’il appartient à une plateforme d’échange ou à un dépositaire connu, Didit enregistre le VASP attribué sur le transfert, l’ajoute à l’annuaire et relance les canaux une fois avec le nouveau nom. Voir Attribution par analyse de la blockchain pour le comportement complet et la tarification. Un transfert dont la destination est déclarée comme portefeuille auto-hébergé ignore les sept canaux - il n’existe aucun VASP de contrepartie avec lequel échanger - et passe directement à la preuve de propriété du portefeuille par l’utilisateur final.
Didit assure aussi le volet de l’échange destiné à la contrepartie : les endpoints TRP publics d’identité et de demande que les autres VASP appellent pour vous joindre, les endpoints de rappel et le flux de récupération par e-mail. Vous ne les appelez jamais vous-même - il s’agit d’une infrastructure gérée - mais la page Interopérabilité Travel Rule documente chacun d’entre eux afin que les VASP des contreparties puissent se connecter à vous. Vous n’interagissez avec Travel Rule que par les endpoints /v3/ décrits sur cette page.

Ce qui s’exécute lors de l’envoi d’un transfert

Les contrôles s’exécutent dans l’unique appel POST /v3/transactions/ : au moment où vous recevez la réponse, le filtrage a été effectué, le transfert est enregistré et les règles ont été évaluées. L’échange lui-même commence après la réponse : Didit choisit le rail et contacte la contrepartie en dehors de votre requête, de sorte qu’aucun réseau ni aucune contrepartie ne peut ralentir ou faire échouer la création. Chaque changement de statut ultérieur arrive sous forme d’un webhook travel_rule.status.updated qui indique le rail. L’objet transfert renvoyé par les endpoints de transfert Travel Rule contient un champ delivery qui indique si le message a bien été transmis. Il n’existe aucun pipeline à interroger : les parties asynchrones sont l’échange, la réponse de la contrepartie et toute action de l’utilisateur final. Étapes :
  1. Validation et rattachement du sujet. La charge utile est validée. Un transaction_id déjà utilisé dans votre application est rejeté avec 400, et lorsque deux requêtes portant le même identifiant arrivent en même temps, l’une d’elles reçoit 409. Le subject est rattaché à votre profil utilisateur ou entreprise via vendor_data, de sorte que le transfert apparaît sur ce profil pour la surveillance. Un sujet ou un vendor_data de contrepartie figurant sur une liste de blocage est rejeté avec 400, et aucune transaction n’est créée. Les portefeuilles, comptes bancaires, adresses IP et appareils figurant sur une liste de blocage sont vérifiés à l’étape 6 et refusent la transaction à ce stade, après l’exécution du routage.
  2. Enrichissement de l’appareil et du réseau. Les adresses IP des parties sont enrichies de données de géolocalisation, et tout contexte d’appareil (automatique lors d’une soumission via le SDK) est normalisé dans le graphe des appareils. C’est le même graphe qu’alimentent les sessions KYC, de sorte que le critère « lié par appareil » fonctionne à travers l’onboarding et les transactions.
  3. Filtrage crypto. Pour les transferts crypto avec la surveillance activée, le portefeuille concerné (ou le hash on-chain) est filtré auprès des outils d’analyse blockchain avant tout échange : exposition aux sanctions, proximité avec un mixeur, fonds volés. Voir Filtrage crypto et des portefeuilles.
  4. Transfert Travel Rule. Le moteur d’échange vérifie le seuil et tient compte d’une déclaration de portefeuille auto-hébergé, puis enregistre le transfert : NOT_APPLICABLE sous votre seuil, UNCONFIRMED_OWNERSHIP pour un portefeuille auto-hébergé déclaré, COMPLETED lorsque l’autre portefeuille est votre portefeuille vérifié, et AWAITING_COUNTERPARTY dans les autres cas, avec un rail encore vide. Chaque changement de statut met en file d’attente un événement travel_rule.status.updated, et Didit l’envoie après le transaction.created de la transaction (étape 7).
  5. Création de la session de preuve de propriété du portefeuille. Si le transfert exige une preuve de contrôle par l’utilisateur final et que auto_wallet_verification est activé, une session du widget de preuve de propriété du portefeuille est créée et renvoyée dans action_required.
  6. Évaluation des règles. Toutes les règles actives s’exécutent : le ensemble EU TFR réagit au statut du transfert défini à l’étape 4, vos règles personnalisées tiennent compte du résultat du filtrage de l’étape 3, et une règle peut bloquer la transaction (IN_REVIEW), la refuser ou exiger une vérification renforcée (AWAITING_USER).
  7. Facturation et notification. Une transaction sortante est facturée sur une seule ligne : la ligne Travel Rule lorsqu’elle lance un échange, et Transaction Screening dans le cas contraire. Un transfert entrant ne génère aucun de ces frais, et une application sandbox n’est jamais facturée. La réponse est renvoyée, transaction.created est envoyé à vos destinations de webhook, suivi des événements travel_rule.status.updated de l’étape 4.
  8. Routage, après la réponse. Didit parcourt les sept rails par ordre de priorité. Si aucun ne permet de résoudre la contrepartie, l’attribution par analyse blockchain identifie la contrepartie et les rails sont relancés une fois. Le premier rail accessible achemine l’échange : le transfert reste AWAITING_COUNTERPARTY sur ce rail tant que la contrepartie ne répond pas, est réglé immédiatement lorsqu’une contrepartie INTERNAL correspondante est trouvée (COMPLETED ou COUNTERPARTY_MISMATCHED_DATA), ou passe à COUNTERPARTY_VASP_NOT_FOUND lorsqu’aucun rail n’atteint la contrepartie. Chaque changement déclenche travel_rule.status.updated, et vos règles sont à nouveau évaluées dès que le statut ou le rail change. Un transfert que le routage laisse en COUNTERPARTY_VASP_NOT_FOUND reçoit à ce moment sa session de widget de preuve de propriété du portefeuille (lorsque auto_wallet_verification est activé). Vous la lisez dans action_required de la transaction avec Get transaction.

Comment chaque étape correspond à vos obligations

Note sur les tests. Les applications sandbox ignorent l’évaluation des règles et la facturation, et n’envoient aucun webhook transaction.created ni transaction.status.updated. Les transactions sandbox restent APPROVED, sauf si un transfert Travel Rule arrivant à expiration applique votre timeout_outcome, et rien n’est facturé. Le moteur d’échange Travel Rule fonctionne lui-même sur les applications sandbox lorsque Travel Rule est activé, avec une seule contrepartie : chaque échange sortant est dirigé vers la Didit Test VASP sur le rail TEST_VASP, qui répond à l’intérieur de Didit selon le scénario que vous choisissez. Le transfert peut ainsi être complété, refusé, présenter une incohérence, devenir injoignable, expirer ou attendre la preuve de propriété du portefeuille. Aucun message de conformité n’atteint jamais un VASP de contrepartie réelle, et les webhooks travel_rule.status.updated sont envoyés avec environment: sandbox. Entrant : le VASP de test peut vous envoyer un transfert (Trigger Test VASP Inbound Transfer), et l’enregistrement (POST /v3/travel-rule/inbound/) est réglé en fonction de votre carnet d’adresses de portefeuilles comme sur une application en production : ON_HOLD, UNCONFIRMED_OWNERSHIP, COUNTERPARTY_MISMATCHED_DATA, ou COMPLETED puis FINISHED. Voir Tester Travel Rule en sandbox.

Cycle de vie

Chaque transfert a un status parmi 13 valeurs. L’échange commence à AWAITING_COUNTERPARTY dans le cas normal, à NOT_APPLICABLE lorsque le montant est inférieur à votre threshold_amount ou qu’une demande de paiement Lightning ne désigne aucun bénéficiaire (voir Lightning), ou à UNCONFIRMED_OWNERSHIP lorsque la destination a été déclarée comme portefeuille auto-hébergé. Il se termine dans l’un des états finaux : FINISHED, CANCELLED ou EXPIRED. Le transfert suit sa propre machine à états, distincte du statut d’examen de la transaction sous-jacente. Un statut d’échange ne modifie jamais à lui seul le statut de la transaction, à une seule exception près : une expiration applique votre timeout_outcome configuré (ou une politique sunrise ou de non-réponse lorsqu’elle s’applique). Vos règles sont en revanche réévaluées à chaque transition : les règles TFR de l’UE ou toute règle portant sur travel_rule_status peuvent donc faire évoluer la transaction, par exemple vers IN_REVIEW. Un transfert qui requiert une preuve de contrôle du portefeuille par l’utilisateur final ajoute un bloc action_required à la transaction, mais son status reste tel que vos règles l’ont laissé. Lisez l’état de l’échange dans travel_rule.status ; lisez le verdict d’autorisation / de suspension / de refus dans le status propre de la transaction. Chaque transition (et chaque statut signalé passivement) est également reflété dans les valeurs personnalisées de la transaction, afin que les règles puissent s’y référer : custom_values.travel_rule_status (statut canonique), custom_values.travel_rule_rail (le rail qui a porté l’échange, vide tant qu’aucun rail n’est sélectionné) et custom_values.travel_rule_regime (dérivé du jurisdiction de vos paramètres : SG → singapore, GB → uk, et tout code d’un État membre de l’UE ou de l’EEE, comme DE ou FR, → eu ; les codes non mappés sont inscrits en minuscules, par exemple US → us. Un props.travel_rule_regime que vous soumettez vous-même prime toujours sur la valeur dérivée).
Ne diffusez jamais avant COMPLETED. Libérer des fonds on-chain alors qu’un transfert est en AWAITING_COUNTERPARTY, UNCONFIRMED_OWNERSHIP ou dans un état de non-concordance ou de refus va à l’encontre de l’objectif de l’échange. Conditionnez votre étape de diffusion à status == "COMPLETED", puis appelez PATCH .../travel-rule/ avec le payment_txn_id obtenu pour passer à FINISHED.
Les transitions en un coup d’œil (CANCELLED est en outre accessible depuis chaque état non terminal avec action: "cancel") :

Livraison et nouvelles tentatives

Didit envoie séparément de la requête qui l’a déclenché chaque message qu’un transfert doit transmettre, et le réessaie jusqu’à ce que l’autre partie le prenne en charge. L’objet de transfert renvoyé par Terminer, annuler ou renvoyer, Confirmer la propriété et l’enregistrement sunrise contient le message le plus récent dans delivery, et c’est aussi le cas de l’objet travel_rule intégré à la transaction ; il vaut null lorsque le transfert n’a jamais eu de message à envoyer. Un échec dont le réseau peut encore se remettre (pas de réponse à temps, échec de connexion, HTTP 408, 429 ou 5xx) est réessayé après 30 secondes, 2 minutes, 10 minutes, 30 minutes puis 1 heure. Lorsqu’un premier message échoue définitivement (le réseau l’a refusé, ou la sixième tentative a également échoué), le transfert passe à COUNTERPARTY_VASP_NOT_REACHABLE avec last_error dans sa timeline, et vous pouvez le réessayer avec action: "resend". Tout autre message qui échoue définitivement est consigné dans la timeline. delivery ne remplace jamais le status du transfert : lisez l’échange à partir de status, et utilisez delivery pour voir pourquoi un transfert attend encore.

Valeurs de statut héritées

Si vous envoyiez déjà travel_rule_details.status avant cette version, ces valeurs fonctionnent toujours et sont converties automatiquement. Les variantes en camelCase de n’importe quel statut (par exemple counterpartyVaspNotFound) sont également acceptées, à la soumission comme dans les valeurs de condition d’une règle, et sont normalisées vers la forme canonique :

Configuration

Configurez votre profil VASP et votre politique de négociation une seule fois par application avec GET/PUT /v3/travel-rule/settings/.
PUT est une mise à jour partielle : n’envoyez que les champs que vous souhaitez modifier.

Rigueur de la correspondance des noms

Modes de validation de la propriété

ownership_validation_mode détermine le niveau de preuve exigé pour qu’un transfert puisse s’appuyer sur la propriété d’un portefeuille : Un portefeuille est mémorisé pour chaque utilisateur final une fois vérifié. En mode FULL et SIMPLIFIED, un transfert ultérieur ne le réutilise que si une preuve acceptée par le mode pour ce transfert le corrobore. Lorsque inbound_auto_accept ne trouve aucun portefeuille correspondant dans le carnet d’adresses, un portefeuille crypto que vous avez enregistré sur les transactions d’un seul utilisateur final identifie le titulaire. Didit ajoute ce portefeuille au carnet d’adresses de l’utilisateur final comme votre auto-déclaration, vérifié sauf si le mode est FULL. Il n’ajoute jamais un portefeuille que le carnet d’adresses référence déjà sous un autre nom de la même chaîne (eth et ethereum), y compris s’il a été enregistré comme appartenant à un autre VASP. Les vérifications d’adresses que d’autres VASP effectuent avant d’envoyer (via GTR, Sygna et VerifyVASP) suivent elles aussi le mode : en FULL, un portefeuille que l’utilisateur final a seulement déclaré n’est pas signalé comme vous appartenant.

Partage de données

data_sharing_fields choisit les champs que Didit envoie à la contrepartie au sujet de vos propres utilisateurs, séparément pour les personnes physiques et les entreprises :
  • Les personnes physiques peuvent partager name, wallet_address, address, national_id, customer_identification, date_of_birth et place_of_birth. Les entreprises utilisent les cinq premiers.
  • name et wallet_address sont toujours envoyés.
  • Un type d’entité que vous n’avez jamais choisi partage tous les champs. Une liste vide est elle aussi un choix : elle ne partage que le nom et le portefeuille.
  • Un PUT fusionne par type d’entité : l’envoi de individual laisse le choix enregistré pour company inchangé.
  • Un choix qui ne peut pas satisfaire les obligations applicables au donneur d’ordre dans le régime de votre jurisdiction (règlement UE sur les transferts de fonds ou Recommandation 16 du GAFI), y compris une liste vide, est refusé avec 400. Les erreurs figurent sous data_sharing_fields, indexées par type d’entité.

Correspondance par champ

matching_rules définit le degré de rigueur avec lequel chaque champ des données de la contrepartie est comparé à celles que vous détenez :
Chaque valeur est NONE, STRICT, CONTAINS, DEFAULT ou FUZZY. Valeurs par défaut : name suit name_matching_strictness, date_of_birth et address valent NONE, et account vaut STRICT. Un champ omis conserve sa valeur par défaut, et un champ que le type d’entité ne compare pas (une entreprise n’a pas de date_of_birth) est refusé avec 400.

Seuils par juridiction

threshold_amount existe parce que le périmètre de la Travel Rule varie selon le régulateur. Didit applique par défaut 0.00, la position de l’UE : chaque transfert est donc échangé, sauf si vous relevez ce seuil délibérément : Définissez threshold_amount au montant fixé par votre régulateur : les transferts inférieurs à ce montant sont résolus en NOT_APPLICABLE sans échange de données d’identité. Les seuils évoluent : confirmez le montant en vigueur avec votre conseil juridique en conformité, plutôt que de considérer ce tableau comme un avis juridique.

Modèle de données IVMS 101

Le standard du secteur pour les données échangées dans le cadre de la Travel Rule est IVMS 101 (interVASP Messaging Standard). travel_rule_details.originator_data et travel_rule_details.beneficiary_data sont des charges utiles de personne au format IVMS 101 : Didit les encapsule dans l’enveloppe native de chaque canal. Le canal TRP échange des blocs IVMS101 (ivms101.2023, indiqué dans le champ ivms_version du transfert), le canal Sygna sa variante IVMS chiffrée en ECIES, et les canaux GTR, TRUST et VERIFYVASP leurs propres enveloppes chiffrées. Une seule structure de charge utile atteint ainsi chaque contrepartie dans le format attendu par son protocole. Didit lit ces clés dans chaque charge utile de personne lorsqu’il construit le message IVMS 101 : first_name et last_name (ou un seul name ou full_name), date_of_birth, place_of_birth, residence_country (ou country), vendor_data (ou customer_identification) et wallet_address. Deux règles pratiques :
  • Écriture latine. IVMS 101 exige des valeurs en système d’écriture latin ; translittérez les noms qui sont écrits nativement dans un autre système d’écriture. La correspondance des noms de Didit compare des ensembles de jetons normalisés : les différences d’ordre et de casse ne font donc pas échouer un échange, mais des systèmes d’écriture différents des deux côtés le feront.
  • Envoyez les données que vous avez vérifiées. Lorsque la personne concernée par vendor_data est un utilisateur disposant d’une vérification KYC Didit approuvée, Didit transmet l’identité vérifiée, et non les valeurs que vous avez saisies : voir Données vérifiées et provenance. Pour toute autre personne, Didit transmet exactement le originator_data que vous envoyez. Construisez-le donc à partir de l’identité que vous avez vérifiée lors de l’onboarding, et non à partir d’un champ de formulaire non vérifié. C’est ce qu’exige la formulation de la FATF : « informations requises et exactes sur le donneur d’ordre ». Sur le canal INTERNAL, l’application du bénéficiaire reçoit aussi le subject que vous avez envoyé (nom, vendor_data, date de naissance, pays et adresse) en tant que contrepartie de sa transaction entrante.

Données vérifiées et provenance

Lorsque la personne concernée par un transfert est l’un de vos utilisateurs, une personne physique disposant d’une vérification KYC approuvée, Didit construit ce côté de l’échange à partir des données vérifiées :
  • le donneur d’ordre d’un transfert sortant que vous créez avec POST /v3/transactions/ ;
  • le bénéficiaire d’un transfert entrant que vous signalez avec POST /v3/transactions/, ou avec POST /v3/travel-rule/inbound/ lorsque le holder_vendor_data du portefeuille correspondant désigne cet utilisateur.
Sur POST /v3/transactions/, les deux cas s’appliquent à une transaction dont transaction_category vaut travel_rule pendant que la Travel Rule est activée. Les noms vérifiés, la date et le lieu de naissance, le numéro et le type de document, l’adresse et le pays de résidence (déduit de cette adresse) prévalent sur les valeurs que vous avez envoyées. Un champ que la vérification ne contient pas conserve votre valeur, de même qu’une adresse complète que vous avez envoyée lorsque l’adresse vérifiée n’est pas assez complète pour être transmise. Les metadata du transfert, dans l’objet travel_rule intégré à la transaction, indiquent d’où provient chaque champ et où vos valeurs différaient :
Les clés *_provenance associent chaque champ à verified ou caller. Les clés *_verified_differences listent chaque champ pour lequel vous avez envoyé une valeur différente de la valeur vérifiée, sans tenir compte de la casse, des espaces ni des codes pays à deux ou trois lettres. Lorsque les données d’un transfert entrant sont masquées, les différences sont omises. Didit calcule ces deux types de clés : les copies de ces clés, ou de name_match, que vous envoyez dans travel_rule_details.metadata sont donc supprimées.

Envoyer directement de l’IVMS 101

En plus des objets à plat originator_data et beneficiary_data, travel_rule_details (sur POST /v3/transactions/) et POST /v3/travel-rule/inbound/ acceptent des données IVMS 101 authentiques :
  • un bloc originator et un bloc beneficiary, avec originatorPersons ou beneficiaryPersons contenant un naturalPerson ou un legalPerson, ainsi que accountNumber ;
  • les mêmes blocs à l’intérieur d’un objet ivms101 (ou IVMS101) ;
  • une personne IVMS 101, ou un bloc complet, en tant que originator_data ou beneficiary_data.
Chaque côté accepte une seule personne : un naturalPerson, ou un legalPerson, ce qui fait de la partie une entreprise. Un originator_data ou beneficiary_data à plat et non vide l’emporte sur un bloc pour le même côté.
Un bloc mal formé renvoie une erreur 400, avec une clé correspondant à son chemin, par exemple {"travel_rule_details": {"originator.originatorPersons": ["must be a non-empty list of originator persons."]}}. Sur POST /v3/travel-rule/inbound/, les mêmes clés se trouvent au niveau supérieur, sous ivms101. lorsque vous avez utilisé l’enveloppe.

Données requises par régime

Pendant que la Travel Rule est activée, un transfert sortant égal ou supérieur à votre seuil doit comporter ce qu’exige son régime, faute de quoi il répond 400. Seuls les côtés que vous avez envoyés sont vérifiés, les données vérifiées comptent pour le donneur d’ordre, et les erreurs apparaissent sous travel_rule_details.originator_data ou travel_rule_details.beneficiary_data, par exemple "EU TFR requires the originator name." ou "FATF Recommendation 16 requires the beneficiary wallet address." Pour le donneur d’ordre, la vérification lit les champs que votre application partage dans data_sharing_fields, car ce sont les seuls qu’un canal transmet. Le bénéficiaire est vérifié tel que vous l’avez envoyé. Lorsque le transfert ne satisfait le régime qu’avec un champ que vous ne partagez pas, le 400 nomme l’exigence du régime, puis les champs que vos paramètres partagent. Le numéro de document officiel est national_id (ou document_number), le numéro d’identification client est customer_identification (ou vendor_data), et la date de naissance est date_of_birth. Un beneficiary_data.wallet_address vide est rempli à partir de counterparty.payment_method.account_id avant la vérification. Un retrait UTXO dont la sortie bénéficiaire a été déduite par Didit à partir de travel_rule_details.utxo est considéré comme la comportant. Un transfert Lightning auquel la Travel Rule ne s’applique pas n’est pas vérifié.

Travel Addresses

Une Travel Address est une chaîne compacte et encodée qui pointe vers l’endpoint TRP d’entrée d’un VASP ; elle n’expose jamais d’URL brute ni d’adresse de portefeuille. Une fois Travel Rule activé, Didit génère votre propre travel_address (renvoyée en lecture seule dans les paramètres) afin que vous puissiez la partager avec les autres VASP qui vous joignent via le canal TRP. Chacun de vos portefeuilles du carnet d’adresses reçoit également un receiving_travel_address propre : une requête envoyée à cette adresse désigne ce portefeuille, de sorte que Didit n’a pas besoin de le retrouver grâce au numéro de compte ou au nom du titulaire. Le serveur TRP public de Didit ne répond pas encore aux autres VASP ; la réception via TRP n’est donc pas disponible tant que ce n’est pas le cas : voir Réseaux Travel Rule. Lorsque vous connaissez la Travel Address d’une contrepartie, enregistrez-la sur l’entrée du carnet d’adresses de portefeuille du portefeuille de destination afin que Didit achemine directement les transferts de ce bénéficiaire via TRP, au lieu de recourir à l’e-mail. Didit valide les Travel Address que vous enregistrez sur une entrée du carnet d’adresses de portefeuille ou envoyez dans originating_vasp : chacune doit commencer par ta et se décoder en une URL de requête contenant t=i, si bien qu’un espace réservé tel que ta... renvoie 400. Un beneficiary_data.travel_address sur une transaction n’est pas validé : si elle ne se décode pas, le canal TRP est ignoré. L’encodage est un Base58Check avec le préfixe ta : l’hôte et le chemin de l’endpoint (qui porte toujours la paire de requête t=i), suivis d’une somme de contrôle de quatre octets issue d’un double SHA-256, le tout encodé en base58. Une adresse réelle ressemble donc à ceci et atteint environ 100 caractères :
Lorsque Didit valide une Travel Address, toute valeur qui n’est pas une adresse bien formée dont la somme de contrôle est valide renvoie 400 avec "Invalid travel address.". Prenez une Travel Address réelle dans la réponse renvoyée par vos paramètres, ou auprès de la contrepartie elle-même.

Tester sans identifiants réseau

Vous n’avez pas besoin d’une adhésion à GTR, TRUST, VerifyVASP ou Sygna pour tester les parcours qui ne nécessitent pas d’adhésion. Vous avez tout de même besoin d’une clé API Didit pour l’application testée.
  • Sandbox sortant : soumettez une transaction travelRule depuis une application sandbox. Après les contrôles de seuil et de portefeuille auto-hébergé, le Didit Test VASP y répond au sein de Didit avec le scénario que vous choisissez selon le portefeuille ou le nom du bénéficiaire : acceptation, refus, nom non concordant, injoignable, expiration du délai ou auto-hébergé. Aucun réseau, aucun e-mail ni aucun autre client de Didit ne reçoit quoi que ce soit.
  • Sandbox entrant : faites envoyer un transfert par le test VASP avec Trigger Test VASP Inbound Transfer, ou enregistrez-en un vous-même avec Register Inbound Travel Rule Transfer. Testez un portefeuille inconnu, un portefeuille non vérifié, une non-concordance de nom de bénéficiaire et un portefeuille vérifié correspondant. Ces scénarios permettent de tester de véritables décisions entrantes sans contacter de réseau.
  • Deux applications en production contrôlées : utilisez le canal INTERNAL entre deux de vos applications en production (l’application destinataire est détectable et prouve la maîtrise du portefeuille de destination au moyen d’un message signé ou d’un dépôt de test), ou une contrepartie TRP que vous contrôlez, pour tester un échange entre deux parties sans identifiants commerciaux de réseau. Les applications en production peuvent entraîner des frais et des notifications normaux ; utilisez des identités de test et des contreparties que vous contrôlez.
Voir Tester Travel Rule en sandbox pour chaque scénario et son résultat exact. Les tests sandbox ou de protocole local réussis ne valident pas les identifiants, l’adhésion ni l’interopérabilité en production d’un réseau commercial. Ceux-ci nécessitent un sandbox de réseau autorisé ou un échange contrôlé en production avec des identifiants d’adhésion valides.

Envoyer un transfert

Incluez le transfert dans POST /v3/transactions/ en définissant transaction_category sur travelRule (ou travel_rule) et en joignant un objet travel_rule_details (alias : travelRule). Le bloc travel_rule_details est facultatif, mais dès que vous le joignez, travel_rule_details.status est obligatoire : un bloc sans ce champ renvoie 400. Lorsque Travel Rule est activé pour votre application, Didit ignore la valeur status que vous envoyez et pilote lui-même l’échange :
  1. Si le montant est inférieur au threshold_amount configuré, le transfert passe immédiatement à NOT_APPLICABLE et aucune négociation n’a lieu.
  2. Si le moyen de paiement de la contrepartie déclare la destination comme portefeuille auto-hébergé (payment_method.method_type: "unhosted_wallet"), aucun canal n’est essayé : le transfert passe directement à UNCONFIRMED_OWNERSHIP et le widget de propriété du portefeuille est généré automatiquement (voir Portefeuilles auto-hébergés déclarés).
  3. Sinon, les canaux sont essayés dans l’ordre de priorité, et le premier capable d’atteindre la contrepartie prend en charge l’échange :
    • INTERNAL : exactement un autre client Didit détient une preuve par message signé ou par dépôt test pour beneficiary_data.wallet_address, établie après le dernier changement de titulaire du portefeuille. Votre propre application ne compte jamais, et lorsque votre utilisateur final a choisi un membre du réseau Didit dans le widget de propriété du portefeuille, seule la preuve de ce membre compte. L’application de ce client est en production (et non en sandbox), a Travel Rule activé et is_discoverable défini, et correspond au VASP que vous avez indiqué lors de l’envoi de counterparty_vasp_lei ou counterparty_vasp_name. Un portefeuille seulement déclaré, prouvé par une capture d’écran, non vérifié ou simplement répertorié dans un carnet d’adresses ne reçoit jamais rien par INTERNAL.
    • TRP : l’actif du transfert dispose d’une correspondance Digital Token Identifier pour le réseau du transfert (voir Identifiants d’actifs TRP), et la charge utile contient beneficiary_data.travel_address, l’entrée du carnet d’adresses de la destination en contient une, ou la contrepartie se résout dans l’annuaire (par counterparty_vasp_lei ou counterparty_vasp_name) avec un point de terminaison TRP.
    • GTR, TRUST, VERIFYVASP, SYGNA : vous avez connecté votre propre adhésion à ce réseau dans Marketplace, et la contrepartie se résout en identifiant de réseau (champ explicite ou entrée de l’annuaire, voir Champs de résolution de la contrepartie). Didit ne détient aucune adhésion de plateforme sur ces réseaux pour l’instant (il est en cours d’onboarding en tant que prestataire), donc disabled_networks ne modifie pas cette étape aujourd’hui.
    • EMAIL : beneficiary_data.counterparty_vasp_email a été fourni, ou counterparty_vasp_name se résout en une entrée de l’annuaire avec un e-mail de conformité.
  4. Si aucun canal ne parvient à résoudre une contrepartie, Didit exécute la chaîne de découverte de la contrepartie. Lorsque vasp_attribution_enabled est activé, cette chaîne comprend l’attribution par analyse de blockchain : le portefeuille de destination est rattaché à son VASP propriétaire, le nom attribué est inscrit dans beneficiary_data.counterparty_vasp_name (si le champ est vide) et dans l’annuaire, puis les canaux sont relancés une fois. Ainsi, un portefeuille attribué à un VASP dont l’entrée de l’annuaire comporte un point de terminaison TRP, un identifiant de réseau ou un e-mail de conformité est acheminé lors de ce second passage.
  5. Si la contrepartie reste non résolue, le transfert passe à COUNTERPARTY_VASP_NOT_FOUND. Pour les transferts sortants, Didit génère automatiquement le widget de propriété du portefeuille (lorsque auto_wallet_verification est activé) afin que le transfert puisse tout de même aboutir comme transfert vers un portefeuille auto-hébergé, et vous pouvez relancer le routage plus tard avec action: "resend" une fois que vous disposez de meilleures données sur la contrepartie.
Les étapes 1 et 2 s’exécutent pendant la requête. Les étapes 3 à 5 s’exécutent juste après la réponse : le transfert revient avec le statut AWAITING_COUNTERPARTY et protocol: null, et chaque résultat arrive sous forme de webhook travel_rule.status.updated.

Identifiants d’actifs TRP

TRP désigne l’actif transféré par son Digital Token Identifier (DTI) ISO 24165. Un DTI identifie un jeton sur une seule blockchain, donc un jeton multi-chaînes possède un DTI par réseau. Didit envoie les transferts via TRP pour ces actifs : Les identifiants USDC sont ceux que l’émetteur du jeton publie dans son livre blanc MiCA. Pour un jeton multi-chaînes, le transfert doit indiquer son réseau dans transaction_details.crypto_params.crypto_chain (par exemple CELO, BASE, OP, ARB ou POL). Sans réseau, ou sur un réseau sans correspondance, le transfert n’est pas envoyé via TRP et Didit essaie les canaux restants : d’abord les réseaux d’interopérabilité, puis l’e-mail. USDT ne dispose pas encore de correspondance. Un jeton sans correspondance DTI, comme le stablecoin propre à une entreprise, nécessite que son émetteur enregistre un DTI auprès de l’autorité d’enregistrement ISO 24165, un par jeton et par blockchain. Une fois les DTI créés, contactez le support Didit avec chaque DTI, sa blockchain et les décimales du jeton, et Didit ajoute la correspondance. Les demandes entrantes TRP qui comportent un DTI par chaîne correspondant sont enregistrées avec la devise du jeton, le montant exprimé dans les décimales du jeton, et le réseau.

Découverte de la contrepartie

Un transfert qui ne comporte qu’une adresse de portefeuille atteint tout de même sa contrepartie dès que quelqu’un peut l’identifier. Avant qu’un transfert sortant soit déclaré non routable, Didit vérifie, dans cet ordre, et s’arrête à la première étape qui désigne un VASP disposant d’un itinéraire : Le champ discovery du transfert liste chaque étape exécutée avec son outcome (match, miss ou skipped), une reason lisible par machine, le vasp et le rail trouvés, ainsi qu’un horodatage. Un VASP que vous ou l’utilisateur final avez choisi prime sur toute étape automatique : la découverte ne le remplace jamais par sa propre supposition. Lorsqu’aucune étape ne désigne de contrepartie, le transfert passe à COUNTERPARTY_VASP_NOT_FOUND, ou à COUNTERPARTY_VASP_NOT_REACHABLE lorsqu’un VASP a été désigné mais qu’aucune de ses routes n’est disponible pour votre application. Le transfert porte alors une reason (awaiting_end_user, no_directory_match, directory_match_no_route, ou picked_member_without_proven_wallet lorsque le membre choisi par l’utilisateur final n’a pas prouvé la propriété du portefeuille) ainsi qu’une liste de next_actions : Le lien du widget correspond à url dans les actions effectuées par l’utilisateur final. Lorsqu’une action de vérification de propriété du portefeuille est en attente, le bloc action_required de la transaction (type wallet_ownership) porte la même reason et les mêmes next_actions, de sorte qu’un client qui suit déjà action_required affiche le sélecteur sans travail supplémentaire.

Champs de résolution de la contrepartie

Tout ce que Didit sait de la contrepartie provient de travel_rule_details.beneficiary_data. Ces champs pilotent la résolution des canaux ; tous sont facultatifs :

Formes de chaîne

Sur une chaîne de comptes (chaînes EVM, Solana, Tron), un transfert déplace des fonds d’une adresse vers une autre, et Didit lit les deux portefeuilles directement sur la transaction. Trois autres formes nécessitent davantage, et ce que Didit a résolu pour elles figure dans le transfert sous chain_details (vide, {}, sur une chaîne de comptes). chain_details.originator et chain_details.beneficiary portent chacun l’address, son memo et la source d’où il provient, et la mise en correspondance et l’acheminement utilisent d’abord les portefeuilles indiqués à cet endroit.

Transferts UTXO

Une transaction Bitcoin, Litecoin, Bitcoin Cash ou Dogecoin dépense des entrées provenant d’un nombre quelconque d’adresses et paie un nombre quelconque de sorties, dont certaines renvoient la monnaie à l’expéditeur. Envoyez les entrées et sorties sous travel_rule_details.utxo (montants dans l’unité propre à la monnaie, sous forme de chaînes décimales), et Didit déduit ce que vous ne déclarez pas :
  • Donneur d’ordre : originator_data.wallet_address lorsque vous l’envoyez, sinon l’adresse d’entrée ayant apporté le montant le plus élevé (source: largest_input).
  • Bénéficiaire : le portefeuille déclaré, sinon l’unique sortie qui ne renvoie pas de monnaie à une adresse d’entrée (only_non_change_output), sinon la sortie correspondant au montant du transfert (amount_match).
Le transfert transporte alors :
chain_details.warnings signale les incohérences : declared_originator_not_an_input, declared_beneficiary_not_an_output, beneficiary_output_ambiguous (aucune sortie unique ne pouvait être distinguée) et beneficiary_output_amount_differs. Un utxo sur toute autre chaîne renvoie une erreur 400, et chaque liste contient au plus 1 000 entrées. Un dépôt signalé reprend le même objet utxo (voir Enregistrement sunrise après dépôt).

Réseaux à mémo et à tag

Sur XRP, Stellar (XLM), EOS, Cosmos (ATOM) et Hedera (HBAR), une plateforme d’échange reçoit tous ses clients sur une même adresse partagée et les distingue grâce au tag de destination ou au mémo. Le mémo fait donc partie du portefeuille : envoyez-le comme payment_method.memo sur le participant, ou comme memo dans originator_data / beneficiary_data (alias destinationTag), et Didit compare l’adresse et le mémo ensemble.
  • Une entrée du carnet d’adresses avec un mémo ne correspond qu’aux transferts qui mentionnent ce mémo. Une entrée sans mémo correspond à l’ensemble de l’adresse. Un transfert qui ne mentionne aucun mémo n’atteint jamais une entrée qui en a un : sur une adresse partagée, un dépôt sans son tag n’appartient à personne.
  • Une X-address XRP contient son tag : Didit la découpe en adresse classique et en tag (source: x_address), si bien qu’un portefeuille enregistré sous l’une ou l’autre forme correspond à un transfert exprimé sous l’autre forme.
  • Chaque mémo sur une adresse partagée constitue son propre moyen de paiement sur la transaction.
  • Formats acceptés par réseau : un tag de destination XRP est un nombre entier de 0 à 4294967295 (un nombre JSON est accepté) ; un mémo Stellar comporte jusqu’à 28 octets de texte, un identifiant sur 64 bits ou un hash de 32 octets en hexadécimal ; EOS accepte jusqu’à 256 octets, Cosmos jusqu’à 256 caractères, Hedera jusqu’à 100 octets. Un mémo sur une chaîne qui n’en a pas (Bitcoin, chaînes EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin, Lightning) renvoie une erreur 400.

Lightning

Un paiement Lightning n’a pas d’adresse de bénéficiaire on-chain : le payeur détient une demande de paiement. Sur un transfert de crypto-monnaie sur Bitcoin ou Lightning, payment_method.account_id peut contenir cette demande, et Didit identifie le bénéficiaire à partir de celle-ci sans contacter aucun réseau : Le bénéficiaire est enregistré comme compte et comme chain_details.beneficiary (source: invoice_payee), sur la chaîne LIGHTNING, et ce que dit la demande figure dans chain_details.lightning. Lorsque la demande ne nomme aucun bénéficiaire, un transfert sortant est NOT_APPLICABLE, avec la raison indiquée dans chain_details.not_applicable_reason et dans sa timeline : LIGHTNING_NO_PAYEE_IDENTIFIER, LIGHTNING_BOLT12_BLINDED_PAYEE ou LIGHTNING_LNURL_NAMES_NO_PAYEE. Une valeur n’est interprétée comme Lightning que sur bitcoin, sur LIGHTNING, ou lorsqu’aucune chaîne n’est indiquée (une Lightning Address uniquement sur bitcoin ou LIGHTNING) : sur toute autre chaîne, c’est l’adresse propre à cette chaîne.
Le canal INTERNAL n’atteint un autre client Didit que par le biais d’un portefeuille dont ce client a prouvé la maîtrise au moyen d’un message signé ou d’un dépôt test, et ces preuves existent aujourd’hui pour Bitcoin, les chaînes EVM, Solana et Tron. Sur les réseaux à mémo, Lightning, Litecoin, Bitcoin Cash et Dogecoin, un transfert n’atteint donc jamais un autre client Didit via INTERNAL. La correspondance par mémo, tag et Lightning s’applique néanmoins à votre propre carnet d’adresses : transferts entrants, dépôts signalés et demandes TRP adressées à vos portefeuilles.

Mise à jour du portefeuille donneur d’ordre

Un retrait est souvent déclaré depuis le portefeuille principal de votre client, et émis depuis les adresses qui détenaient les fonds. Enregistrez le portefeuille d’où les fonds sont réellement partis avec PATCH /v3/transactions/{transaction_id}/travel-rule/ :
  • Transferts sortants uniquement, et jamais un transfert CANCELLED ou EXPIRED (409).
  • Lorsque les entrées UTXO du transfert sont connues, le portefeuille doit en faire partie (sinon 400). Envoyez aussi memo sur un réseau à mémo.
  • Seul l’enregistrement est modifié : rien n’est envoyé à nouveau à la contrepartie et le statut reste inchangé. chain_details.originator devient le nouveau portefeuille avec source: updated, la timeline enregistre l’ancien et le nouveau portefeuille, et la contrepartie INTERNAL du transfert est mise à jour de la même manière.

Attribution par analyse de la blockchain

Lorsque vous ne transmettez qu’une adresse de portefeuille, Didit peut tout de même identifier qui se trouve de l’autre côté. Si aucun canal ne peut acheminer le transfert, Didit compare le portefeuille de destination aux données d’attribution de l’analyse blockchain ; la même analyse que celle qui alimente le filtrage des transactions crypto sait quels portefeuilles appartiennent à quelles plateformes d’échange et quels dépositaires. L’attribution ne s’exécute que sur les transferts sortants qui comportent à la fois un portefeuille de destination et un chain (voir Champs de résolution de la contrepartie) ; sans chaîne, elle est ignorée. Lorsque le portefeuille est attribué à une entité connue, Didit :
  1. Enregistre l’attribution sur le transfert : beneficiary_data.counterparty_vasp_name est renseigné (si vous ne l’avez pas fourni), et les métadonnées du transfert contiennent un bloc vasp_attribution avec le nom de l’entité, son type, son pays et source: "blockchain_analytics".
  2. Ajoute l’entité à l’annuaire VASP (ou rapproche l’entrée existante), afin que l’attribution reste stable d’un transfert à l’autre.
  3. Relance une fois les canaux avec le nom attribué : une entrée d’annuaire qui comporte déjà un endpoint TRP permet d’acheminer immédiatement le transfert, au lieu de le faire aboutir à COUNTERPARTY_VASP_NOT_FOUND.
L’appel d’attribution tient lieu de filtrage crypto complet : la transaction reçoit l’ensemble des résultats de filtrage (score de risque, catégorie de risque du portefeuille, instantané du réseau), exactement comme si la surveillance crypto avait été effectuée, et custom_values.aml_screening_trigger de la transaction prend la valeur travel_rule_attribution, afin que vous puissiez voir que le filtrage a été exécuté parce que le routage Travel Rule l’exigeait. Lorsque la surveillance crypto a déjà filtré le même portefeuille sur cette transaction, le résultat enregistré est réutilisé : pas de second appel d’analyse, pas de second débit. L’attribution ne bloque jamais la création de la transaction : si la recherche d’analyse échoue ou si votre solde ne suffit pas à en couvrir le coût, le transfert passe simplement à COUNTERPARTY_VASP_NOT_FOUND et la tentative n’est pas facturée. Avec action: "resend", une attribution enregistrée précédemment est réutilisée au lieu d’être interrogée à nouveau. Désactivez toute l’étape avec vasp_attribution_enabled: false dans les paramètres. Consultez Tarifs pour savoir comment l’appel d’attribution est facturé. Lorsque Travel Rule est désactivé, travel_rule_details (y compris un status fourni par l’appelant, ancien ou actuel) est tout de même enregistré sur la transaction, mais de façon passive : aucun rail n’est déclenché. Utilisez cette option pour compléter rétroactivement les données de conformité que vous suiviez déjà avec votre propre processus. Un transfert sortant passif n’ouvre aucun échange : il est donc facturé comme Filtrage des transactions, et non sur la ligne Travel Rule. Lorsque Travel Rule est activé, cette requête est acheminée via le rail EMAIL, car elle indique l’e-mail de conformité de la contrepartie et aucun rail précédent ne peut atteindre le bénéficiaire :
La réponse, résumée aux champs importants ici :
  • originator_data correspond à ce que vous avez envoyé, car le sujet de l’exemple n’a pas de vérification KYC approuvée ; pour un utilisateur vérifié, Didit le construit à partir des données vérifiées (voir Données vérifiées et provenance). Le rail EMAIL n’affiche au destinataire que les champs du donneur d’ordre que vous partagez dans data_sharing_fields.
  • beneficiary_data.chain vaut ETH car la requête n’indiquait aucune chaîne ; Didit l’a déduite de la devise.
  • status vaut APPROVED car aucune règle n’a agi sur la transaction ; vos propres règles peuvent lui attribuer un autre statut.
  • Le contact de conformité reçoit le lien de récupération, et le transfert se termine, échoue au contrôle de correspondance du nom ou est refusé lorsque le contact répond, ou expire s’il ne répond pas (voir Récupération par e-mail).
protocol reste null car le rail est choisi après la réponse (par exemple EMAIL lorsque vous avez fourni counterparty_vasp_email) : Obtenir la transaction l’affiche une fois le rail choisi, et chaque webhook travel_rule.status.updated ultérieur le contient dans rail. Si le portefeuille du bénéficiaire appartient à un autre client Didit qui en a prouvé le contrôle, le rail INTERNAL règle l’ensemble de l’échange juste après la réponse, et ce webhook indique déjà COMPLETED (ou COUNTERPARTY_MISMATCHED_DATA). Lorsque le nom ne correspond pas, votre transfert passe en COUNTERPARTY_MISMATCHED_DATA et l’autre client ne reçoit rien. Sans counterparty_vasp_email, et lorsque ni les rails ni l’attribution ne peuvent atteindre le bénéficiaire, le transfert finit plus tard en COUNTERPARTY_VASP_NOT_FOUND avec une action wallet_ownership sur la transaction.
L’objet travel_rule intégré à la transaction contient les champs propres au transfert (uuid, status, protocol, required, obligations_count, originator_data, beneficiary_data, metadata), les éléments que Didit a déterminés et comparés (chain_details, name_match, data_match, mismatched_fields, ainsi que les champs de masquage data_masked et data_unmasked), et les champs qui indiquent où en est le transfert : direction, rail, counterparty_vasp, delivery, discovery, reason et next_actions (voir Pourquoi un transfert est bloqué). Chaque ligne de GET /v3/transactions/ contient le même bloc, ou null pour une transaction sans transfert. Les endpoints de transfert dédiés ci-dessous renvoient aussi ces champs, ainsi que ivms_version, ownership_confirmed, deadline_at, timeline et created_at, mais ni obligations_count ni metadata.
Pour clôturer un transfert sortant COMPLETED une fois qu’il a été diffusé on-chain :
Cela exige que le transfert soit un transfert sortant au statut COMPLETED, sinon l’API renvoie 409. En cas de succès, le transfert sortant et le transfert correspondant sur le rail INTERNAL passent tous deux à FINISHED, et le payment_txn_id de la transaction est défini sur le hash que vous avez fourni. Sur le rail TRP, Didit publie également le hash sur le callback de confirmation de la contrepartie. Pour annuler un transfert qui n’a pas encore atteint un état final, envoyez plutôt {"action": "cancel"} au même endpoint. Le transfert correspondant sur le rail INTERNAL est également annulé, et sur le rail TRP, une fois que la contrepartie a approuvé la demande, Didit publie l’annulation sur son callback de confirmation. Un transfert déjà en FINISHED, CANCELLED ou EXPIRED renvoie 409. Une requête sans payment_txn_id ni action pris en charge renvoie 400. Sur un transfert passé par GTR, VERIFYVASP ou SYGNA, Didit signale le hash on-chain (ou l’annulation) au réseau après la réponse, avec les mêmes mécanismes de nouvelle tentative ; le champ delivery de la réponse indique ce message. Pour relancer un transfert sortant qui n’a pas pu atteindre sa contrepartie (un transfert actuellement en COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE ou NOT_ENOUGH_COUNTERPARTY_DATA), améliorez d’abord les données de la contrepartie, par exemple en ajoutant un travel_address à l’entrée du portefeuille de destination, un contact pour l’entrée de la contrepartie dans l’annuaire, ou en ajoutant le VASP à l’annuaire. Envoyez ensuite {"action": "resend"} au même endpoint. Didit relance le délai de confirmation et met à nouveau en file la séquence de routage depuis le début, en utilisant les données stockées sur le transfert : la réponse montre le transfert de nouveau en AWAITING_COUNTERPARTY (avec resend queued dans sa timeline), et le résultat arrive sous forme de webhook travel_rule.status.updated. Une relance envoyée pendant qu’une tentative précédente est encore en cours est envoyée une fois cette tentative terminée. Tout autre statut, ou un transfert entrant, renvoie 409.
Pour désigner vous-même la contrepartie lorsque la découverte n’a pas pu le faire, envoyez {"action": "beneficiary_vasp", "beneficiary_vasp": "<directory VASP id>"} (l’id provenant de GET /v3/travel-rule/vasps/). Le transfert est acheminé vers ce VASP, puis envoyé à nouveau. Seul un transfert sortant en COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE ou NOT_ENOUGH_COUNTERPARTY_DATA accepte cette action ; tout autre statut renvoie 409, et un identifiant inconnu renvoie 404.

Pourquoi un transfert est bloqué

Un transfert en COUNTERPARTY_VASP_NOT_FOUND ou COUNTERPARTY_VASP_NOT_REACHABLE indique pourquoi il est bloqué et ce qui le fait avancer, à chaque lecture de sa transaction. Le bloc travel_rule de GET /v3/transactions/{transaction_id}/, du 201 de POST /v3/transactions/ et de chaque ligne de GET /v3/transactions/ le contient, de même que l’objet de transfert renvoyé par le endpoint de transfert :
  • reason est la cause lisible par la machine, "" pour tout autre statut.
  • next_actions liste les actions qui font avancer le transfert, [] pour tout autre statut.
  • delivery est le message le plus récent que le transfert doit envoyer à l’autre partie : kind, status (QUEUED, SENDING, RETRYING, DELIVERED ou FAILED), rail, attempts, last_error, next_attempt_at et delivered_at, ou null lorsqu’il n’en doit aucun.
  • discovery indique où Didit a cherché la contrepartie, avec une entrée par étape exécutée : step, outcome (match, miss ou skipped), reason, vasp, rail et at.
email_undeliverable et delivery_failed proviennent d’une livraison échouée et n’apparaissent que sur COUNTERPARTY_VASP_NOT_REACHABLE. Une livraison échouée détermine d’abord reason, de sorte qu’une contrepartie que Didit a trouvée n’est jamais présentée comme introuvable. Les quatre autres proviennent de la trace de découverte, quel que soit le statut. Un renvoi fait avancer le transfert, donc son reason est effacé. La ligne de la chronologie correspondant à une transition provoquée par une livraison échouée porte la même valeur sous reason. Si le fournisseur de messagerie refuse l’e-mail de conformité du transfert d’exemple ci-dessus, le bloc travel_rule de sa transaction se présente, en version abrégée :
Ici, delivery concorde avec reason, car l’envoi lui-même a été refusé. Un refus lors d’une relance, ou un rejet signalé plus tard, laisse delivery.status à DELIVERED : le premier e-mail est bien parti. Pour savoir pourquoi un transfert est bloqué, lisez reason, et non delivery. Pour corriger, indiquez à nouveau l’e-mail de conformité de la contrepartie, puis relancez :
update_compliance_email conserve l’adresse comme beneficiary_data.counterparty_vasp_email de ce transfert : elle n’appartient qu’à ce transfert et n’est rattachée à aucune entrée d’annuaire ni à aucune liste de contacts. Le statut ne change pas, la chronologie reçoit compliance email updated, et le transfert garde reason: email_undeliverable jusqu’à ce que le renvoi envoie la demande à la nouvelle adresse. Seul un transfert sortant dans COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE ou NOT_ENOUGH_COUNTERPARTY_DATA accepte cette action ; tout autre transfert reçoit une réponse 409, et un compliance_email absent ou mal formé reçoit une réponse 400.

Portefeuilles auto-hébergés déclarés

Lorsque votre client vous indique que la destination est son propre portefeuille auto-hébergé (non hébergé), déclarez-le sur la transaction plutôt que de laisser le routage découvrir qu’aucun VASP ne se trouve derrière : définissez le type de moyen de paiement de la contrepartie sur unhosted_wallet.
La déclaration court-circuite entièrement le routage :
  • Aucun rail n’est essayé, car il n’existe aucun VASP de contrepartie avec lequel échanger, et le transfert est créé directement dans UNCONFIRMED_OWNERSHIP.
  • Lorsque auto_wallet_verification est activé (valeur par défaut), Didit génère automatiquement une session de widget de vérification de la propriété du portefeuille liée à la transaction et la renvoie dans le bloc action_required de la transaction ; les SDK la lancent automatiquement lors de la soumission côté client.
  • Dès que le client prouve le contrôle du portefeuille, le transfert effectue la vérification de correspondance du nom du bénéficiaire et passe à COMPLETED (ou à COUNTERPARTY_MISMATCHED_DATA si la correspondance échoue).
Cela correspond, sous forme d’API, à l’obligation du règlement TFR de l’UE relative aux portefeuilles auto-hébergés : vérifier la propriété du portefeuille non hébergé au lieu d’échanger des données avec un VASP de contrepartie. La déclaration est lue dans le moyen de paiement côté destination d’un transfert sortant et côté source d’un transfert entrant : dans les deux cas, il s’agit du participant contrepartie. Un transfert déclaré non hébergé suit déjà son chemin final sans rail, donc UNCONFIRMED_OWNERSHIP n’est pas un statut permettant un renvoi : terminez la preuve de propriété (ou annulez) au lieu d’appeler action: "resend".

Réception

Confirmation de la propriété du portefeuille

Un transfert reste en UNCONFIRMED_OWNERSHIP dans deux cas :
  • Un transfert entrant correspond à une entrée du carnet d’adresses dont la propriété n’est pas encore vérifiée.
  • Le portefeuille de la contrepartie a été déclaré auto-hébergé (counterparty.payment_method.method_type: "unhosted_wallet"), pour un retrait sortant ou un dépôt entrant. Voir Portefeuilles auto-hébergés déclarés.
Votre application confirme ou refuse le transfert :
Le corps de la requête accepte confirmed (obligatoire, booléen) et un wallet_address facultatif. L’endpoint accepte un transfert dans l’un de ces états et renvoie 409 avec {"detail": "Transfer is not awaiting ownership confirmation."} pour tout autre état :
  • UNCONFIRMED_OWNERSHIP, dans les deux sens.
  • ON_HOLD sur un transfert entrant.
  • UNCONFIRMED_OWNERSHIP, ON_HOLD ou COUNTERPARTY_MISMATCHED_DATA sur un transfert entrant TRP, afin qu’un responsable de la conformité puisse traiter manuellement une demande de la contrepartie.
Sur un transfert créé par Didit lui-même (rail INTERNAL, enregistrement sunrise ou portefeuille auto-hébergé déclaré) :
  • confirmed: true marque l’entrée correspondante du carnet d’adresses de portefeuilles comme propriété vérifiée, puis lance la correspondance de nom avec le holder_name de cette entrée : COMPLETED si elle réussit, COUNTERPARTY_MISMATCHED_DATA sinon. La contrepartie sur le rail INTERNAL, lorsqu’elle existe, reçoit le même résultat. Pour un portefeuille auto-hébergé déclaré, confirmed: true enregistre que votre client a vérifié qui contrôle le portefeuille de la contrepartie et termine le transfert (COMPLETED), en sortie comme en entrée. Un transfert entrant dont le dépôt a déjà été réglé passe directement à FINISHED.
  • confirmed: false fait passer les deux côtés du transfert à COUNTERPARTY_VASP_GENERAL_DECLINE.
  • L’entrée correspondante est recherchée dans votre propre carnet d’adresses uniquement par adresse de portefeuille, sans tenir compte de la chaîne. Résoudre un ON_HOLD entrant sans entrée correspondante crée l’entrée à partir de beneficiary_data.wallet_address du transfert et la marque comme vérifiée. Un portefeuille auto-hébergé déclaré fait exception : il est enregistré dans votre carnet d’adresses comme propriété vérifiée, comme décrit ci-dessous. Dans tous les autres cas, une entrée absente renvoie 409 avec {"detail": "wallet address entry no longer available"}, ce qui est aussi la réponse pour une entrée supprimée.
Sur un transfert entrant TRP, le même appel répond à la demande de la contrepartie :
  • confirmed: true nécessite une adresse de paiement à renvoyer, prise dans le wallet_address de la requête, puis dans l’entrée correspondante, puis dans beneficiary_data.wallet_address. Si aucune des trois n’est disponible, la requête renvoie 400 avec {"wallet_address": ["wallet_address is required to approve."]}. Didit crée ou vérifie l’entrée, envoie à la contrepartie le rappel de résolution de la demande, définit ownership_confirmed et fait passer le transfert à COMPLETED.
  • confirmed: false envoie une résolution de refus et fait passer le transfert à COUNTERPARTY_VASP_GENERAL_DECLINE.
  • Si l’endpoint de rappel de la contrepartie est injoignable, la requête renvoie 502 et le transfert conserve son statut actuel. En cas d’approbation, l’entrée du carnet d’adresses du portefeuille est tout de même créée ou marquée comme vérifiée.
Pour un portefeuille auto-hébergé déclaré, le portefeuille que vous vérifiez est celui de la contrepartie : la destination d’un retrait sortant, ou la source (le portefeuille du donneur d’ordre) d’un dépôt entrant. Didit l’ajoute au carnet d’adresses comme propriété vérifiée au nom de la contrepartie, de sorte que le portefeuille est comparé au nom de la contrepartie (le donneur d’ordre, pour un dépôt). Si le carnet d’adresses contient déjà ce portefeuille sous un autre nom de titulaire, le transfert passe à COUNTERPARTY_MISMATCHED_DATA à la place. Si le transfert ne désigne aucun portefeuille de contrepartie, transmettez wallet_address dans le corps de la requête. Lorsque ni le transfert ni la requête ne fournissent d’adresse de portefeuille, la requête est rejetée avec 400.
  • Un transfert par réseau déjà traité sur son réseau (transfert VerifyVASP, ou transfert GTR ou Sygna Bridge déjà tranché) renvoie 409 (voir Transferts depuis un réseau).
La réponse est l’objet de transfert plus complet décrit ci-dessus. Un nouveau transfert INTERNAL n’attend jamais à cette étape : il n’atteint qu’un client ayant déjà prouvé le contrôle du portefeuille. Un ancien transfert INTERNAL encore en attente, antérieur à cette règle, masque les coordonnées de l’expéditeur tant que vous n’avez pas prouvé le contrôle du portefeuille (message signé ou dépôt test), et sa confirmation renvoie 409 jusque-là. Le refus reste possible. Lorsque la contrepartie a indiqué que le portefeuille est auto-hébergé (aujourd’hui, le Didit Test VASP donne cette réponse en sandbox), la confirmation ajoute le portefeuille à votre carnet d’adresses au nom du bénéficiaire et lance la correspondance de nom : une correspondance termine le transfert et marque le portefeuille comme vérifié.

Transferts depuis un réseau

Didit peut aussi recevoir des transferts Travel Rule qu’une contrepartie envoie via GTR, VERIFYVASP ou SYGNA. La réception est activée par Didit pour chaque réseau séparément, et aucun n’est activé aujourd’hui : elle requiert aussi l’enregistrement des endpoints de Didit auprès de chaque réseau, et Didit ne dispose pas encore d’adhésion à ces réseaux. Les transferts TRUST ne peuvent pas être reçus. Une fois un réseau activé, un message qu’il délivre est traité ainsi :
  • Routage. Le message vous parvient lorsqu’il désigne le code de participant de votre propre adhésion connectée sur ce réseau. Un message adressé à l’adhésion propre de Didit ne vous parvient que si vous êtes l’unique client Didit actif ayant prouvé le contrôle du portefeuille bénéficiaire. Tout autre message est refusé, afin qu’aucun client ne reçoive jamais le transfert d’un autre client.
  • Décision. Didit crée le transfert entrant sur votre application et le décide comme une demande TRP : correspondance du portefeuille, propriété, correspondance des noms et inbound_auto_accept.
  • Une seule fois. Les réseaux renvoient un message tant qu’il n’a pas reçu de réponse. Une répétition reçoit la réponse de la première copie, et rien n’est créé ni décidé deux fois.
  • Votre examen, sur GTR et SYGNA. Un transfert qui attend votre décision (UNCONFIRMED_OWNERSHIP, ON_HOLD ou COUNTERPARTY_MISMATCHED_DATA) se décide avec POST .../travel-rule/ownership/ : confirmed: true l’approuve (transmettez wallet_address lorsque le transfert n’en désigne aucun) et confirmed: false le refuse. Didit répond au réseau après la réponse, avec les mêmes nouvelles tentatives. Un transfert déjà traité renvoie 409.
  • Pas de réponse ultérieure sur VERIFYVASP. VerifyVASP obtient sa réponse au moment où il la demande : un transfert qui nécessiterait votre examen est refusé immédiatement et ne peut plus être décidé ensuite (409).

Données de la contrepartie masquées

Tant que la propriété du portefeuille n’est pas confirmée, les données personnelles de la contrepartie d’un virement entrant sont renvoyées masquées plutôt qu’omises. La première lettre de chaque mot est conservée, et les informations de portefeuille, de chaîne et d’acheminement VASP restent lisibles. data_masked vaut true, et les données de la contrepartie dans le détail, la liste et l’export CSV de la transaction sont masquées de la même manière. Une fois la propriété confirmée, les valeurs réelles apparaissent et data_unmasked enregistre l’entrée d’audit :
reason vaut ownership_confirmed, wallet_proof, address_book_match, own_verified_wallet, inquiry_approved (vous avez approuvé une demande TRP ou un message GTR ou Sygna), known_payment_method (le portefeuille de l’utilisateur final, présent dans vos registres, a répondu) ou travel_address (l’adresse Travel Address propre à chaque portefeuille a désigné ce portefeuille). Un virement INTERNAL entrant porte data_unmasked avec reason: wallet_proof dès sa création, car seul un destinataire ayant prouvé la détention du portefeuille en reçoit un. actor est l’e-mail de l’utilisateur de la console ou le libellé de la clé API, et reste vide pour une décision automatique. Le rapport Journal des virements Travel Rule (CSV et PDF) masque de la même manière les noms d’un virement entrant tant que la propriété n’est pas confirmée. Les rapports que vous déposez auprès d’un régulateur, comme le XML goAML et le PDF du rapport réglementaire, contiennent les données réelles de la contrepartie.

Retour sur les écarts

Un virement COUNTERPARTY_MISMATCHED_DATA indique désormais les champs qui diffèrent. data_match contient la comparaison et mismatched_fields liste les champs. Les deux ne contiennent que des noms de champs et des résultats, jamais des valeurs, afin de rester lisibles lorsque les données sont masquées :
La chronologie mentionne les champs, par exemple « la date de naissance du bénéficiaire ne correspond pas au titulaire du portefeuille ». Un rejet TRP après un écart indique au VASP émetteur les champs qui diffèrent, par exemple « Les données du bénéficiaire ne correspondent pas au titulaire du portefeuille : nom ». Ce que l’autre VASP apprend est volontairement limité : tant que le nom est incorrect, le nom est le seul champ signalé (mismatched_fields: ["name"], tous les autres étant not_compared). Une fois le nom concordant, chaque champ qui diffère est nommé. Cela s’applique au côté émetteur d’un échange INTERNAL et à un rejet TRP. Un nom absent des deux côtés est not_compared et ne constitue pas un écart, et un pays est comparé selon ce qu’il désigne, de sorte que ES et ESP concordent.

Score de correspondance du nom

Chaque virement dont le nom a été comparé porte name_match, et l’entrée de chronologie qui a tranché porte le même objet à côté de son detail :
  • score est la similarité des deux noms complets, de 0 à 100, et vaut 0 lorsque l’un des deux est vide. matched est le résultat au niveau strictness, de sorte que les deux peuvent diverger : FUZZY tranche sur son propre ratio de similarité de 0.85.
  • matched_names sont les mots du nom du virement retrouvés dans l’autre nom ; mismatched_names sont les mots que l’un des deux côtés possède et que l’autre n’a pas.
  • method vaut none, exact_token, token_subset, token_containment ou sequence_ratio, pour NONE, STRICT, DEFAULT, CONTAINS et FUZZY.
  • compared_with vaut wallet_holder (le titulaire enregistré pour le portefeuille) ou counterparty_vasp (le nom donné par la contrepartie sur la page de réponse par e-mail, ou la réponse du Didit Test VASP).
  • L’issue du virement suit data_match, qui compare chaque champ. Un virement peut donc être COUNTERPARTY_MISMATCHED_DATA alors que name_match.matched vaut true.
  • name_match figure dans l’objet virement renvoyé par les endpoints Travel Rule et dans travel_rule intégré à la transaction, et vaut null tant qu’aucun nom n’a été comparé. La liste des virements et les webhooks ne le contiennent pas.
  • Les noms sont masqués tant que les parties sont masquées, et sur la copie de l’autre client d’un échange INTERNAL.

Messages et demandes de documents

Deux VASP peuvent résoudre un virement contesté dans Didit plutôt que par e-mail. GET et POST /v3/transactions/{transaction_id}/travel-rule/messages/ listent et envoient des messages sur un virement :
  • kind vaut MESSAGE (par défaut) ou DOCUMENT_REQUEST. Une demande de document exige au moins une entrée dans requested_documents (jusqu’à 10) ; un message exige un body (jusqu’à 5000 caractères) ou un fichier.
  • reply_to désigne un message antérieur du même virement. Une réponse de l’autre côté qui contient un document satisfait la demande de document à laquelle elle répond, et fulfilled_at y est renseigné. Votre propre réponse ne satisfait jamais votre propre demande.
  • Envoyez en multipart avec files pour joindre jusqu’à 5 fichiers de 10 Mo chacun, au format PDF, PNG, JPEG ou WebP. Didit vérifie le contenu de chaque fichier, et pas seulement son type déclaré. Tout autre fichier renvoie 400 sur files (« Joignez uniquement des fichiers PDF, PNG, JPEG ou WebP. »).
  • Un virement peut contenir jusqu’à 100 messages, dont jusqu’à 20 peuvent porter des pièces jointes (chacune avec jusqu’à 5 fichiers), en comptant les deux côtés. Au-delà de l’une ou l’autre limite, la requête renvoie 400. Les pièces jointes des messages ne comptent pas dans les documents propres à la transaction.
  • Les messages fonctionnent sur les canaux INTERNAL et EMAIL. Sur INTERNAL, l’autre client Didit voit le message de son côté de la paire. Sur EMAIL, la contrepartie reçoit un e-mail et répond dans le portail de retrait. Si cet e-mail de notification ne peut pas être envoyé, le message est tout de même enregistré et la requête renvoie 201.
  • La requête renvoie 409, avec une raison dans detail, pour un virement sur TRP, GTR, TRUST, VerifyVASP ou Sygna (ces protocoles ne transportent pas de messages), un virement sans contrepartie pour l’instant, un virement INTERNAL dont les données ne correspondaient pas au titulaire du portefeuille (rien n’a atteint le destinataire), un virement INTERNAL dont le destinataire n’a pas encore prouvé le contrôle du portefeuille (des deux côtés, tant qu’il ne l’a pas fait), un virement EMAIL dont la contrepartie n’a plus d’adresse e-mail de conformité, et un virement EMAIL dont le lien de retrait a expiré ou dont le virement est clos.
  • Aucun webhook n’est déclenché lorsque la contrepartie répond. Consultez l’endpoint des messages.
  • L’e-mail qui informe la contrepartie d’un nouveau message est envoyé en anglais.

Carnet d’adresses de portefeuilles

Enregistrez les portefeuilles que vous contrôlez afin que les virements entrants (canal INTERNAL, demandes TRP entrantes et enregistrements après dépôt) puissent être rapprochés de ces portefeuilles, avec POST/GET /v3/travel-rule/wallet-addresses/ et PATCH/DELETE /v3/travel-rule/wallet-addresses/{entry_uuid}/.
PATCH accepte holder_name, holder_vendor_data, entity_type et travel_address, et ne renvoie que ces quatre champs. DELETE supprime l’entrée (204) : son adresse est libérée pour un nouvel enregistrement et elle cesse immédiatement de correspondre aux nouveaux transferts.
La correspondance ne tient pas compte de la casse pour l’adresse, inclut son memo sur un réseau à mémo, et est limitée par chain. Lorsqu’un transfert porte une chaîne, une entrée ne correspond que si son chain a la même valeur (sans tenir compte de la casse) ou si cette valeur est vide. Les noms de chaînes sont comparés comme du texte, sans traduction : une entrée enregistrée sous ethereum ne correspond pas à un transfert dont la chaîne est ETH. Les transferts sortants tirent leur chaîne de beneficiary_data.chain, puis de transaction_details.crypto_params.crypto_chain, puis de custom_properties.crypto_chain, puis de la devise (par exemple ETH, ou pour USDT et USDC la chaîne déduite de payment_reference_id), uniquement lorsqu’il s’agit d’une chaîne reconnue par Didit ; les enregistrements faits après le dépôt prennent leur chaîne dans leur champ chain, en minuscules. Les demandes TRP entrantes ignorent la chaîne : elles correspondent par adresse ou, lorsque la demande ne nomme aucun portefeuille, par nom du bénéficiaire parmi vos entrées vérifiées. POST .../travel-rule/ownership/ ne correspond aussi que par adresse. Lorsque plusieurs entrées correspondent, le rail INTERNAL et les enregistrements faits après le dépôt privilégient une entrée dont l’actionnariat est vérifié, puis la plus ancienne ; la recherche TRP sortante prend l’entrée la plus ancienne qui stocke une Travel Address. Les demandes TRP entrantes et l’endpoint d’actionnariat prennent l’une quelconque des entrées correspondantes ; gardez donc une seule entrée par adresse lorsque c’est possible. Le rail INTERNAL ne correspond à aucune entrée lorsque des entrées vérifiées existent pour l’adresse dans plus d’une autre application.

Import en masse de portefeuilles

Importez jusqu’à 10 000 portefeuilles en un seul appel avec POST /v3/travel-rule/wallet-addresses/import/, au format JSON ({"entries": [...]} ou un tableau nu) ou sous forme de fichier CSV UTF-8 d’au plus 5 Mo avec une ligne d’en-tête. Une ligne désigne un portefeuille par son address, par address_hash (le SHA-256 de l’adresse en minuscules, soit 64 caractères hexadécimaux ou 44 caractères base64), ou par les deux, ainsi que n’importe lequel des champs d’entrée ci-dessus. Envoyez uniquement le hachage pour enregistrer un portefeuille sans divulguer son adresse : il correspond aux transferts vers cette adresse comme n’importe quelle autre entrée, et peut être prouvé avec le widget de vérification de l’actionnariat du portefeuille.
L’import est idempotent et renvoie un statut pour chaque ligne, dans l’ordre d’envoi : created, existing (le même portefeuille, la même chaîne et le même memo figurent déjà dans le carnet et n’ont pas été modifiés), duplicate (répète une ligne précédente, désignée dans duplicate_of_row) ou failed avec ses errors. Une ligne en erreur ne fait jamais échouer les autres : la réponse est 200 lorsqu’aucune ligne n’a échoué et 207 lorsqu’une ligne a échoué, avec les compteurs dans total, created, existing, duplicate et failed. Une cellule CSV vide est considérée comme non envoyée, et les colonnes que l’import ne lit pas sont ignorées ; un export provenant d’un autre outil s’importe donc tel quel. Sur GET /v3/travel-rule/transfers/, chaque ligne contient originator_memo et beneficiary_memo à côté des deux portefeuilles.

Récupération par e-mail pour les contreparties injoignables

Lorsqu’aucun rail de protocole ne peut atteindre la contrepartie, Didit se rabat sur EMAIL : il envoie un lien de récupération à usage unique à beneficiary_data.counterparty_vasp_email (ou à une adresse déterminée à partir de beneficiary_data.counterparty_vasp_name dans le registre de l’annuaire VASP). Le destinataire n’a besoin ni d’un compte Didit ni d’une clé API : le token du lien sert d’authentification. Déroulement de l’envoi :
  • Aux bonnes adresses. Une counterparty_vasp_email que vous transmettez est le seul destinataire. Sinon, Didit écrit à trois adresses au maximum : les contacts de la contrepartie qui n’ont pas rebondi (les vôtres d’abord, puis ceux de la plateforme, en donnant la priorité à ceux qui ont déjà répondu), puis l’e-mail de conformité de l’entrée de l’annuaire lorsqu’un emplacement reste libre. Chaque adresse reçoit son propre e-mail.
  • En votre nom. L’e-mail provient de l’adresse de Didit sous le nom « votre nom légal via Didit », dans la langue de la juridiction de la contrepartie (l’anglais lorsqu’elle n’est pas connue), et le lien ouvre la page de réponse dans cette langue. Vous ne recevez aucune copie : suivez la chronologie du transfert.
  • Avec des relances. Tant que la contrepartie n’a pas répondu, Didit la relance 72, 24 et 6 heures avant l’échéance, au moins 12 heures après la demande ou la dernière relance, et jamais après l’échéance.
  • Avec la gestion des rebonds. Un rebond ou une plainte pour spam marque le contact atteint comme invalide, avec sa raison, et la chronologie du transfert le consigne. Un contact de la plateforme est alors invalide pour tous les clients de Didit ; le contact propre à votre application ne l’est que pour votre application. Lorsque l’envoi d’e-mails de Didit cesse totalement pour une adresse (après un rebond permanent, une plainte pour spam ou un désabonnement), chaque contact à cette adresse est marqué invalide (suppressed), et ces contacts redeviennent valides dès que l’envoi vers l’adresse reprend. Lorsqu’il ne reste aucune adresse à laquelle écrire, le transfert passe à COUNTERPARTY_VASP_NOT_REACHABLE, avec la raison du serveur de messagerie destinataire dans sa chronologie. Un rebond permanent ou une plainte pour spam sur la counterparty_vasp_email que vous avez transmise produit cet effet immédiatement, car c’est le seul destinataire ; un rebond temporaire (soft_bounce) sur cette adresse, ou sur l’e-mail de conformité de l’entrée de l’annuaire, laisse la demande ouverte pour les relances.
Le lien ouvre une page hébergée qui affiche l’échange en attente et appelle, pour le destinataire, ces deux endpoints publics. Pour le transfert d’exemple ci-dessus, les détails de récupération sont :
reference correspond à l’uuid du transfert. originator_data et le ivms_request IVMS 101 qui en est construit ne contiennent que les champs du donneur d’ordre que vous partagez dans data_sharing_fields ; le nom et le portefeuille sont toujours inclus. fiat_amount et fiat_currency sont vides lorsque vous n’avez envoyé aucune valeur convertie, contact reprend la valeur de ?contact= du lien, et decline_reasons liste les valeurs acceptées par decline_reason. join_invitation.eligible vaut true tant que la contrepartie dispose d’une invitation active à rejoindre Didit ; le lien d’invitation lui-même n’est transmis que par e-mail. Cet endpoint renvoie le montant enregistré tel quel, avec les 18 décimales complètes, et non la forme tronquée utilisée par l’objet de détail de la transaction.
decision vaut accept ou decline. En cas d’accept, le beneficiary_name fourni est comparé au beneficiary_data.name du transfert selon votre règle de nom : COMPLETED en cas de correspondance, COUNTERPARTY_MISMATCHED_DATA sinon. La contrepartie peut aussi envoyer une réponse IVMS 101 complète sous la forme de ivms_response ; son nom de bénéficiaire prévaut alors sur beneficiary_name. En cas de decline, le transfert passe à COUNTERPARTY_VASP_GENERAL_DECLINE. La contrepartie peut indiquer un decline_reason parmi decline_reasons, accompagné d’un decline_detail (512 caractères maximum, obligatoire pour other) ; la chronologie de votre transfert l’enregistre, par exemple counterparty declined: The beneficiary is not our customer. La réponse contient le {"status": "..."} résultant, plus decline_reason après un refus. Une fois que la contrepartie a répondu, les deux endpoints renvoient 410. Les deux endpoints renvoient 404 pour un token inconnu et 410 une fois la fenêtre close : soit le transfert a quitté AWAITING_COUNTERPARTY, soit son échéance de confirmation (confirmation_timeout_hours après la création ou le dernier renvoi) est passée. La contrepartie lit et répond via le lien de récupération avec GET et POST /v3/travel-rule/pickup/{token}/messages/. Cet endpoint ne nécessite aucune clé API, n’accepte que les pièces jointes PDF, PNG, JPEG ou WebP, et renvoie 410 une fois le lien expiré ou le transfert clos. Un message que vous avez envoyé affiche le nom de votre VASP comme author et comme uploaded_by de la pièce jointe, jamais la personne ou la clé API à l’origine de l’envoi.

Dépôts non rapprochés

Un dépôt entrant peut arriver avant les données Travel Rule du VASP d’origine. Un tel dépôt reste en AWAITING_COUNTERPARTY ou NOT_ENOUGH_COUNTERPARTY_DATA tant que les données n’ont pas été reçues. Listez les dépôts encore en attente avec une clé API disposant de l’autorisation transactions :
La réponse utilise les mêmes lignes que GET /v3/travel-rule/transfers/, limitées aux dépôts entrants en attente des données du VASP d’origine. Lorsque les données sont arrivées sous la forme d’un message Travel Rule reçu distinct, rapprochez-les vous-même :
{transaction_uuid} désigne le dépôt. transaction_id désigne la transaction du message Travel Rule reçu. L’appel renvoie { "deposit": ..., "message": ... }. Il renvoie 404 lorsque l’une des deux transactions n’existe plus, et 409 avec travel_rule_match_conflict (« This deposit can no longer be matched. ») lorsque l’une ou l’autre des transactions a déjà changé d’état.

Enregistrement a posteriori (sunrise)

Lorsqu’un dépôt crypto est réglé on-chain avant tout échange Travel Rule, parce que le VASP émetteur était injoignable ou que vous reprenez des dépôts historiques, enregistrez-le a posteriori avec POST /v3/travel-rule/inbound/.
originating_vasp.travel_address doit être une Travel Address réelle et valide au regard de sa somme de contrôle : une valeur factice renvoie 400 avec "Invalid travel address.". originating_vasp.lei doit être vide ou comporter 20 caractères au format LEI (18 lettres ou chiffres, suivis de 2 chiffres) ; toute autre valeur renvoie 400 avec "Invalid LEI: expected 20 alphanumeric characters (ISO 17442).". Didit vérifie uniquement ce format, pas les chiffres de contrôle ISO 17442. Didit crée une transaction travelRule entrante (ou réutilise un dépôt entrant déjà soumis avec le même hash et le même portefeuille, s’il n’a pas encore de transfert Travel Rule) et rapproche le transfert de votre carnet d’adresses de portefeuilles, à partir de l’adresse et de son memo :
  • Aucune entrée correspondante -> ON_HOLD. Résolvez-la avec POST .../travel-rule/ownership/ : confirmed: true ajoute le portefeuille à votre carnet d’adresses comme vérifié et lance la correspondance du nom ; en cas de correspondance, le transfert passe à COMPLETED, puis à FINISHED.
  • L’entrée existe mais la propriété n’est pas prouvée -> UNCONFIRMED_OWNERSHIP.
  • Le nom du bénéficiaire ne correspond pas au holder_name de l’entrée -> COUNTERPARTY_MISMATCHED_DATA.
  • Entrée vérifiée et nom correspondant -> COMPLETED, puis immédiatement FINISHED (le dépôt a déjà été réglé).
Avec l’entrée vérifiée du carnet d’adresses enregistrée dans l’exemple ci-dessus, la réponse est :
Le txn_id de la nouvelle transaction est tr-in-, suivi des 80 premiers caractères du txid et d’un hash de 12 caractères calculé à partir du txid et du portefeuille. created vaut true (HTTP 201) lorsque cet appel a enregistré le dépôt, et false (HTTP 200) lorsque le même txid et le même wallet_address étaient déjà enregistrés ; dans ce cas, le transfert existant est renvoyé sans modification. Sur le chemin avec entrée vérifiée, les deux transitions ont lieu avant que l’endpoint ne réponde : la réponse synchrone affiche donc déjà le statut final FINISHED - l’étape intermédiaire COMPLETED reste néanmoins enregistrée dans le timeline du transfert. Chaque transition déclenche un webhook travel_rule.status.updated (COMPLETED et FINISHED en déclenchent chacun un). Lorsque Didit a créé une nouvelle transaction, elle envoie d’abord transaction.created, puis ces événements. L’enregistrement n’entraîne ni frais Travel Rule ni frais de surveillance. Lorsque la surveillance crypto est activée et que le chain est un réseau que Didit peut filtrer, un dépôt nouvellement créé est également filtré et facturé une seule fois au tarif Wallet Verification & Screening.

Widget de propriété du portefeuille

Pour faire passer un transfert hors de UNCONFIRMED_OWNERSHIP, ou pour vérifier un portefeuille à l’avance avant l’arrivée de tout transfert, générez un widget hébergé de propriété de portefeuille avec POST /v3/travel-rule/widget-session/ et envoyez votre client à l’url renvoyée. Le widget prouve le contrôle du portefeuille par signature de message (y compris pour les comptes de contrat intelligent EVM, via ERC-1271 et ERC-6492), par un test de Satoshi, une capture d’écran ou une auto-déclaration, puis marque l’entrée correspondante du carnet d’adresses comme vérifiée afin que l’échange puisse se poursuivre. Une capture d’écran fait exception : elle ne vérifie jamais le portefeuille à elle seule. Votre équipe de conformité l’examine donc et confirme un transfert en UNCONFIRMED_OWNERSHIP avec POST .../travel-rule/ownership/. Consultez Widget de propriété du portefeuille pour le parcours complet, les méthodes de preuve et la remarque importante : le widget doit être ouvert dans un navigateur réel, jamais dans une webview d’un SDK mobile.

Annuaire

GET /v3/travel-rule/vasps/ interroge l’annuaire des VASP : les entrées de l’annuaire partagé ainsi que les membres du réseau Didit. Chaque ligne indique comment votre application peut joindre ce VASP aujourd’hui.
search correspond à un fragment de nom ou d’alias, à un LEI exact, ou à un domaine (counterparty.example ou une URL de ce domaine). Affinez les résultats avec : Les membres du réseau Didit ne sont pas notés : risk et responsiveness ne renvoient que des entrées de l’annuaire. Les résultats sont triés par nom et paginés avec limit (par défaut 50) et offset. Un réseau, un code pays, un niveau de risque ou un statut de réactivité inconnu renvoie 400 en nommant le champ. GET /v3/travel-rule/vasps/{vasp_id}/ renvoie une seule ligne à partir de son id. Votre propre profil de VASP n’apparaît jamais dans vos résultats. dd_status, dd_score, dd_score_breakdown, dd_assessed_at, dd_score_semantic, risk_level, responsiveness et essential_checks contiennent l’évaluation de vigilance partagée d’une entrée de l’annuaire. decision, entity_screening et questionnaires sont les dossiers propres à votre application concernant le VASP, et aucun autre client ne les voit. Voir Vigilance sur les contreparties pour la signification de chacun, et utilisez ces champs comme conditions avec les champs de règle de contrepartie plutôt que de les tester directement dans le code. Les membres du réseau Didit ne sont pas notés : dd_status vaut COMPLETED, dd_score vaut null, risk_level est vide et le détail du score est vide.

Routes

routes liste les moyens par lesquels votre application peut livrer au VASP, dans l’ordre dans lequel l’échelle de routage les essaie. reachable vaut true lorsqu’au moins une route est available :
  • TRP lorsque l’entrée comporte un point de terminaison TRP vérifié par l’annuaire.
  • GTR, TRUST, VERIFYVASP, SYGNA lorsqu’un réseau répertorie le VASP sous un code. La route n’est available que si votre application peut envoyer via ce réseau aujourd’hui (via: own_membership pour votre propre adhésion connectée, via: didit_membership pour celle de Didit). Sinon, elle est unavailable avec reason: no_membership ou reason: network_switched_off, et n’est jamais présentée comme un moyen d’atteindre le VASP. Les adhésions de Didit aux réseaux ne sont pas encore opérationnelles : aujourd’hui, une route réseau n’est donc disponible que via une adhésion que vous avez connectée dans la Marketplace.
  • EMAIL lorsqu’il existe un contact de conformité pour l’entrée.
  • INTERNAL pour un membre du réseau Didit.
Une inscription TRISA apparaît sous networks et vous indique qui est le VASP sur TRISA, mais Didit ne dispose pas de canal TRISA : elle n’est donc jamais une route. Une application sandbox n’envoie qu’au VASP de test Didit. Aucune route de l’annuaire ne lui est donc disponible : chaque route est unavailable avec reason: sandbox_application, chaque ligne a reachable: false, et reachable=true ne renvoie aucune ligne.

Marques et leurs entités

Une marque qui opère via plusieurs entités réglementées est une seule ligne dont entities désigne les entités juridiques qui reçoivent ses transferts ; une ligne d’entité renvoie à sa marque dans umbrella. Un transfert qui désigne la marque est acheminé vers l’une de ses entités, choisie parmi toutes : celle de la juridiction indiquée dans beneficiary_data.counterparty_vasp_jurisdiction, sinon l’entité liée au portefeuille de destination par l’attribution d’analyse blockchain, sinon l’unique entité de la marque lorsqu’aucune juridiction n’est indiquée. Ce n’est qu’ensuite que cette entité doit être joignable ; si elle ne l’est pas, le transfert ne part jamais vers une autre entité ni vers la marque à la place. Lorsqu’aucune entité ne convient (deux conviennent à égalité, ou la juridiction indiquée ne correspond à aucune d’elles), la route propre à la marque est utilisée ; sans route, le transfert est COUNTERPARTY_VASP_NOT_FOUND. Le choix de l’entité juridique qui reçoit les données du donneur d’ordre n’est jamais une supposition, et n’est jamais déterminé par l’entité qui dispose par hasard d’un contact. Plusieurs entrées portant le même nom, comme des sociétés d’un groupe partageant un nom commercial, sont départagées par la même règle : celle de la juridiction indiquée, sinon celle à laquelle l’attribution d’analyse blockchain renvoie. Lorsque ni l’une ni l’autre n’en désigne une, aucune n’est retenue, et les détails sont considérés comme ne désignant aucune entrée de l’annuaire.

Contacts

contacts sont les adresses auxquelles la route EMAIL envoie les e-mails. Les contacts scope: platform servent tous les clients Didit : Didit les sélectionne (source: staff), ou une adresse sur l’un des domaines propres au VASP a répondu à une demande via son lien de réponse (source: learned). Les contacts scope: application sont privés à votre application : ceux que vous ajoutez (source: application), et une adresse hors des domaines propres au VASP qui a répondu à l’une de vos demandes (source: learned). Un transfert envoie d’abord un e-mail au contact propre à votre application, puis à un contact de plateforme ayant déjà répondu (verified_at renseigné), puis aux autres. Gérez les contacts propres à votre application avec l’API des contacts de contrepartie :
  • GET /v3/travel-rule/counterparty-contacts/ liste chaque contact auquel votre application peut écrire, y compris les contacts de plateforme et ceux qui ont rebondi, du plus récent au plus ancien, paginés avec limit et offset. Filtrez avec ?counterparty= et l’id d’une entrée de l’annuaire ; une valeur qui n’est pas un UUID renvoie 400.
  • POST ajoute un contact privé (counterparty, email, name facultatif) et renvoie 201. Ajouter une adresse que l’entrée possède déjà parmi vos propres contacts renvoie ce contact. Une entrée inconnue renvoie 400 ("No directory entry with this id.").
  • DELETE /v3/travel-rule/counterparty-contacts/{contact_uuid}/ supprime l’un de vos propres contacts (204). Les contacts de plateforme ne peuvent pas être supprimés et renvoient 404.
Un contact dont l’e-mail a rebondi ou fait l’objet d’une plainte porte is_invalid: true, avec invalid_reason (hard_bounce, soft_bounce, spam_complaint ou suppressed), invalid_detail et invalid_at, et le rail EMAIL l’ignore. created_by est vide sur les contacts de la plateforme et sur les contacts ajoutés par une clé API.

Comment les entrées sont ajoutées à l’annuaire

  • Le registre intérimaire MiCA de l’ESMA des prestataires de services sur crypto-actifs, consulté chaque semaine : le nom de chaque prestataire, son LEI et son État membre d’origine.
  • L’annuaire public TRISA, consulté chaque semaine : qui est chaque membre, son pays et son inscription TRISA. Toute inscription que l’annuaire n’affiche plus pendant trois semaines est supprimée.
  • Le registre mondial LEI complète le LEI, les autres raisons sociales et le pays d’une entrée, et retrouve le LEI d’une entrée à partir de sa raison sociale exacte dans son pays.
  • Attribution par analyse blockchain ajoute ou associe le VASP qui possède un portefeuille de destination, et conserve le nom attribué comme alias.
  • L’équipe Didit organise les entrées : identifiants de routage et inscriptions sur les réseaux qu’elle a vérifiés, contacts de conformité, alias, marques et leurs entités, doublons fusionnés et entrées masquées.
Les données qu’un client signale au sujet d’une contrepartie, comme un originating_vasp entrant ou un Travel Address, restent sur le transfert de ce client et ne modifient jamais une entrée partagée. Les entrées sont rapprochées d’abord par LEI, puis par nom exact ou alias, et enrichies plutôt que dupliquées.

Le réseau Didit

Votre profil VASP est membre du réseau Didit, de sorte que les autres clients Didit le trouvent dans l’annuaire et dans le sélecteur de VASP de l’utilisateur final, uniquement si toutes les conditions suivantes sont remplies :
  • votre application est en production (et non en sandbox) et la Travel Rule est activée ;
  • is_discoverable vaut true et legal_name est renseigné ;
  • compliance_email est renseigné et confirmé : envoyez la confirmation avec POST /v3/travel-rule/settings/compliance-email/confirmation/ et ouvrez le lien reçu par e-mail. Modifier l’adresse nécessite une nouvelle confirmation ;
  • Didit n’a pas marqué l’application comme une application de test, de démonstration ou interne.
Le rail INTERNAL effectue lui-même la livraison vers un portefeuille dont vous avez prouvé la propriété, comme décrit dans Envoi d’un transfert : il exige que votre application soit en production, que la Travel Rule soit activée et que is_discoverable soit vrai, et non le reste de la liste. GET /v3/travel-rule/settings/ renvoie didit_network : listed, compliance_email_confirmed, et dans missing chaque condition encore non remplie (travel_rule_off, sandbox_application, not_discoverable, legal_name_missing, compliance_email_missing, compliance_email_unconfirmed, excluded_by_staff, internal_organization).

Identifiants de routage

Le routage sortant consulte une entrée de l’annuaire chaque fois que votre transfert désigne la contrepartie par counterparty_vasp_lei ou counterparty_vasp_name (un alias fonctionne aussi). Une entrée porte un identifiant par réseau sur lequel elle est joignable, un endpoint TRP vérifié et ses contacts de conformité, et routes indique lesquels de ces éléments votre application peut utiliser. Vous pouvez toujours contourner l’annuaire pour un seul transfert en transmettant les champs explicites décrits dans Champs de résolution de la contrepartie.

Diligence raisonnable sur la contrepartie

Avant que les fonds de vos clients n’atteignent un autre VASP, vous devez savoir qui est ce VASP et si vous l’acceptez. La diligence raisonnable sur une contrepartie comporte deux niveaux :
  • L’évaluation partagée. Chaque entrée de l’annuaire porte un score de diligence raisonnable, son niveau de risque, un statut de réactivité et un rapport de contrôles essentiels. Didit les calcule à partir des données propres à la plateforme, de sorte que tous les clients voient les mêmes valeurs.
  • Vos propres dossiers. Votre décision concernant le VASP, le filtrage des sanctions et de la presse négative le concernant, ainsi que les questionnaires de diligence raisonnable échangés avec lui, appartiennent uniquement à votre application. Aucun autre client ne les voit, et ils ne modifient jamais l’entrée partagée.
Vos propres dossiers fonctionnent aussi bien pour une entrée de l’annuaire que pour un membre du réseau Didit : identifiez-les à l’aide de l’id affiché par l’annuaire.

Score et niveau de risque

dd_score va de 0 à 100, et plus la valeur est élevée, plus c’est sûr. Chaque résultat l’indique dans dd_score_semantic, qui vaut toujours higher_is_safer. risk_level est la tranche dans laquelle se situe le score : Le score est la somme de sept composantes. dd_score_breakdown affiche chacune d’elles sous la forme {points, max, reason, detail} : max correspond au poids de la composante, reason est un code lisible par une machine que vous pouvez traduire, et detail est une explication en anglais d’une ligne. Les points sont arrondis à une décimale, et le total à un nombre entier. history se base sur les mêmes requêtes que la réactivité, sans fenêtre temporelle : un échange COMPLETED ou FINISHED est un succès, tandis qu’un échange EXPIRED, COUNTERPARTY_MISMATCHED_DATA ou COUNTERPARTY_VASP_GENERAL_DECLINE est un échec. Une annulation relève de l’initiative de l’expéditeur et n’est jamais comptabilisée. Didit recalcule l’évaluation lorsqu’un échange avec l’entrée est terminé, achevé, expiré ou en discordance, ou refusé, lorsqu’il actualise ou gère l’entrée, et pour chaque entrée lors de l’actualisation hebdomadaire de l’annuaire. dd_assessed_at indique la date de sa dernière exécution. Une entrée qui n’a pas encore été évaluée n’a pas de dd_assessed_at, a un dd_score de 0 et un risk_level vide ; lisez donc risk_level plutôt que le score brut. Les membres du réseau Didit ne sont pas notés : leur dd_score vaut null, leur risk_level est vide et leur dd_score_breakdown est vide.

Contrôles essentiels

essential_checks est un rapport succinct sur quatre contrôles que Didit effectue sur chaque entrée de l’annuaire, uniquement à partir des sources vérifiées de la plateforme, jamais à partir de données signalées par un client : Chaque contrôle de checks indique son status (pass, fail, ou unknown lorsqu’aucune source enregistrée ne permet de trancher), une reason lisible par une machine et un detail en anglais d’une ligne. passed, failed, unknown et total comptent les contrôles, level vaut essential, et checked_at indique le moment où le rapport a été établi. La composante essentials note la part des contrôles décidés qui réussissent : un contrôle unknown ne compte donc ni pour ni contre l’entrée. Le rapport est vide tant que l’actualisation hebdomadaire de l’annuaire n’a pas vérifié une première fois l’entrée, et il reste toujours vide pour un membre du réseau Didit.

Réactivité

responsiveness indique si le VASP répond aux demandes Travel Rule :
total compte les requêtes que les clients de Didit ont envoyées au VASP au cours des 90 derniers jours et dont le résultat est définitif, et answered celles auxquelles il a répondu :
  • Comptabilisées : les requêtes envoyées par un canal réseau ou protocolaire, ainsi que les requêtes envoyées par e-mail à une adresse partagée, c’est-à-dire l’adresse de conformité de l’entrée ou un contact de la plateforme.
  • Jamais comptabilisées : une requête envoyée par e-mail à une adresse saisie par un client, ou à un contact privé d’un client. Cet échange reste dans l’historique de transferts du client et ne qualifie jamais le VASP aux yeux des autres.
  • Répondues : COMPLETED, FINISHED, COUNTERPARTY_VASP_GENERAL_DECLINE ou COUNTERPARTY_MISMATCHED_DATA. Un refus ou une discordance constitue une réponse : le VASP a répondu.
  • Sans réponse : EXPIRED.
  • Ni l’un ni l’autre : tous les autres statuts. Une requête CANCELLED relève de l’initiative de l’expéditeur, COUNTERPARTY_VASP_NOT_REACHABLE peut être un échec du côté de l’expéditeur, et une requête encore en cours n’est pas encore décidée.
Les membres du réseau Didit répondent au sein de Didit et affichent toujours UNKNOWN avec des compteurs à zéro. Prenez des mesures à l’encontre d’une contrepartie qui ne répond pas avec la politique de non-réponse ou le préréglage travel-rule-counterparty-unresponsive.

Votre décision sur une contrepartie

Enregistrez le verdict de votre équipe de conformité sur une contrepartie avec PUT /v3/travel-rule/vasps/{vasp_id}/decision/ :
  • La décision est privée pour votre application : aucun autre client ne la voit, et elle ne modifie jamais l’entrée partagée.
  • Une décision ne change rien à elle seule et ne modifie jamais l’acheminement d’un transfert. Elle s’applique à vos transactions par l’intermédiaire du champ de règle counterparty_dd_status : installez les préréglages, ou écrivez votre propre règle sur ce champ.
  • Une décision sur une marque couvre chacune de ses entités qui n’a pas de décision propre, et l’entité affiche la marque dans inherited_from. Une décision sur l’entité remplace celle de la marque pour cette entité.
  • GET renvoie la décision applicable, ou {"decision": null}. DELETE supprime votre propre décision et renvoie celle qui s’applique ensuite : la décision de la marque pour une entité, sinon null. Il répond 404 lorsque votre application n’a aucune décision propre sur la contrepartie.
  • reason est facultatif, limité à 255 caractères, et chaque PUT le remplace. decided_by est l’utilisateur de la console qui a pris ou modifié en dernier la décision, et il est vide lorsqu’une clé API l’a fait.

Filtrage de la contrepartie

Le filtrage de la contrepartie vérifie le VASP lui-même, en tant qu’entreprise, contre les listes de sanctions et la presse négative. Il est facultatif et facturé : activez-le avec counterparty_screening_enabled dans vos paramètres. Tant qu’il est désactivé, rien n’est filtré ni facturé.
  • Facturation. Chaque nouveau filtrage coûte le prix du Filtrage AML pour entreprises de votre organisation. GET /v3/travel-rule/vasps/{vasp_id}/screening/ renvoie ce price, fresh_days et si le filtrage est enabled, sans frais. Un filtrage datant de moins de 30 jours est considéré comme récent : POST le renvoie à nouveau avec 200 et outcome: reused sans rien facturer, sauf si vous envoyez refresh: true. Un nouveau filtrage répond 201 avec outcome: screened. Les applications sandbox ne sont jamais filtrées ni facturées.
  • Filtrage automatique. Lorsque le filtrage est activé, Didit filtre aussi la contrepartie vers laquelle est effectué un échange sortant dès le début de celui-ci, avant que vos règles n’évaluent la transaction, avec la même réutilisation et la même facturation. Un refus ou un échec à cette étape ne fait jamais échouer le transfert.
  • Résultat. status vaut Approved lorsqu’aucune correspondance possible ne demande d’attention, et In Review ou Declined pour une correspondance possible avec des sanctions ou de la presse négative. flagged vaut true tant qu’un filtrage récent est In Review ou Declined.
  • Effet. Tant que votre dernier filtrage de la contrepartie est signalé et que le filtrage reste activé, counterparty_risk_level vaut HIGH dans vos règles, de sorte que le préréglage travel-rule-counterparty-high-risk, une fois installé, met en attente ses transferts. Le risk_level partagé du répertoire ne change pas.
  • Confidentialité. Le filtrage est privé pour votre application : aucun autre client ne le voit, et il ne modifie jamais l’entrée partagée. Le répertoire affiche votre dernier filtrage sous entity_screening.
  • Refus. 409 lorsque le filtrage est désactivé, pour une application sandbox, ou lorsqu’un filtrage de la même contrepartie est déjà en cours. 403 avec code: insufficient_credits lorsque votre solde ne couvre pas le prix. 502 lorsque le filtrage n’a pas pu être terminé. Aucun de ces cas ne facture quoi que ce soit.

Questionnaire de diligence raisonnable

Demandez à une contrepartie ses réponses au questionnaire de diligence raisonnable standard de Didit. POST /v3/travel-rule/vasps/{vasp_id}/questionnaires/ l’envoie par e-mail à une adresse de conformité enregistrée pour la contrepartie, avec un lien à usage unique vers une page de réponse :
  • Destinataire. sent_to doit être une adresse de conformité enregistrée : pour une entrée du répertoire, son adresse de conformité ou un contact que votre application voit dans contacts ; pour un membre du réseau Didit, son e-mail de conformité confirmé. Omettez-le pour utiliser l’adresse à laquelle est envoyé un e-mail Travel Rule à la contrepartie : d’abord votre propre contact, puis un contact de la plateforme, puis l’adresse de conformité de l’entrée. Didit n’envoie jamais le questionnaire ailleurs, et répond 400 lorsque la contrepartie n’a aucune adresse de conformité enregistrée.
  • Réponses. Le lien ouvre /v3/travel-rule/questionnaires/{token}/, une page destinée à une personne de la contrepartie, qui ne nécessite ni compte ni clé API. Elle répond à chaque question, jusqu’à 2 000 caractères chacune (N/A lorsqu’une question ne s’applique pas), et soumet une seule fois. Une contrepartie peut aussi envoyer {"answers": {"<question id>": "<answer>"}} au même lien. Un lien déjà utilisé, remplacé ou dépassant sa date limite affiche une page d’expiration à la place. L’e-mail et la page sont traduits ; les questions elles-mêmes restent en anglais, afin que leur sens ne change jamais d’une langue à l’autre.
  • Délais. La contrepartie dispose de 30 jours pour répondre (expires_at). Les réponses restent valables pendant 365 jours (valid_until), et current vaut true jusque-là. Un questionnaire resté sans réponse après sa date limite, et des réponses au-delà de leur validité, passent à EXPIRED.
  • Un seul questionnaire ouvert par contrepartie. Un nouvel envoi remplace votre questionnaire précédent resté sans réponse pour la même contrepartie : il passe à EXPIRED et son lien cesse de fonctionner. Un seul e-mail par minute peut être envoyé à une adresse ; un envoi plus rapide répond 429.
  • Lecture des réponses. GET /v3/travel-rule/vasps/{vasp_id}/questionnaires/ liste vos questionnaires avec la contrepartie, du plus récent au plus ancien, avec answers indexées par identifiant de question une fois répondues. Le répertoire les affiche sous questionnaires. Elles sont privées pour votre application.
Le formulaire, version 1, pose ces douze questions :

Transferts avec une seule contrepartie

GET /v3/travel-rule/transfers/?counterparty={vasp_id} liste les transferts Travel Rule de votre application avec une seule contrepartie, du plus récent au plus ancien, selon l’id affiché par l’annuaire : vos transferts dont la contrepartie est cette entrée d’annuaire, ou vos échanges INTERNAL avec ce membre du réseau Didit. Une valeur qui n’est pas un identifiant valide renvoie 400. Chaque ligne affiche reason juste après status : la reason propre au transfert, afin que la liste vous permette de distinguer un e-mail de conformité rejeté d’une contrepartie sans itinéraire, sans ouvrir chaque transfert.

Politiques de période de transition et d’absence de réponse

Lorsqu’un transfert expire (le délai confirmation_timeout_hours est écoulé alors qu’il était AWAITING_COUNTERPARTY), timeout_outcome détermine le sort de sa transaction. Deux paramètres vous permettent de traiter différemment deux motifs courants de délai dépassé : Les deux acceptent HOLD, REJECT ou PROCEED, avec la même signification que pour timeout_outcome, ou une chaîne vide pour suivre timeout_outcome. Les deux sont vides par défaut, de sorte que rien ne change tant que vous n’en définissez pas un :
La première de ces politiques qui s’applique détermine le résultat :
  1. sunrise_policy, lorsqu’elle est définie et que la juridiction de la contrepartie figure dans sunrise_jurisdictions.
  2. non_responsive_policy, lorsqu’elle est définie et que la contrepartie est UNRESPONSIVE.
  3. timeout_outcome dans tous les autres cas, y compris pour un transfert vers un membre du réseau Didit ou vers une contrepartie sans entrée d’annuaire.
sunrise_jurisdictions est en lecture seule et vaut actuellement ["CO", "KH", "VN"]. Seuls ces pays sont considérés comme étant en période de transition : toute autre juridiction, ainsi qu’une juridiction vide ou inconnue, suit timeout_outcome, de sorte qu’une contrepartie ne bénéficie jamais de votre politique de période de transition par erreur. La chronologie du transfert expiré indique le résultat appliqué, par exemple sunrise_policy=PROCEED (counterparty jurisdiction CO does not enforce the Travel Rule yet). Vos règles continuent de s’exécuter après l’expiration, de sorte qu’une règle telle que tr_eu_expired_review du TFR européen peut toujours faire passer la transaction en examen sous PROCEED.

Champs de règle et préréglages

Quatre champs de règle contiennent ce que votre application sait de la contrepartie avec laquelle l’échange Travel Rule d’une transaction a été réalisé : son entrée d’annuaire ou, pour un échange INTERNAL, le client Didit qui l’a reçu. Chaque champ est vide lorsque l’échange n’a atteint aucune contrepartie connue. Cinq préréglages du bundle travel_rule agissent sur ces champs. Installez-les depuis Transactions → Rules → Library dans la Business Console, ou avec l’API de la bibliothèque de règles : Les cinq préréglages ne s’appliquent qu’aux transactions Travel Rule.

Webhooks

Abonnez une destination à travel_rule.status.updated pour être notifié à chaque transition de statut de chaque transfert de votre application, sortant comme entrant, y compris les tronçons sur le rail INTERNAL. Le routage s’exécute après le retour de la création de la transaction. Des résultats tels que COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE, ou un échange INTERNAL réglé immédiatement, vous parviennent donc par cet événement et non dans la réponse à la création.
rail reste vide tant qu’aucun rail n’est sélectionné. Pour une même transaction, Didit envoie travel_rule.status.updated après transaction.created : lorsque Didit crée la transaction (votre POST /v3/transactions/, un enregistrement sunrise, une demande TRP entrante, ou le tronçon entrant sur le rail INTERNAL), il envoie d’abord transaction.created. Chaque événement est livré et réessayé de manière indépendante. Un transaction.created qui a nécessité une nouvelle tentative peut donc arriver après un événement de statut. Basez votre gestionnaire sur transaction_id, et lisez la transaction lorsqu’un événement de statut en désigne une que vous n’avez pas encore vue. Un transfert envoyé via TRUST ne reçoit aucun événement COMPLETED : TRUST ne renvoie rien que Didit puisse lire. Sauf si l’envoi échoue ou si vous l’annulez, son dernier événement est donc EXPIRED. Via GTR, VERIFYVASP et SYGNA, l’événement suit la réponse que Didit lit sur le réseau. Les applications sandbox reçoivent aussi cet événement, avec "environment": "sandbox". Un transfert auquel le Didit Test VASP a répondu porte rail: "TEST_VASP", test_vasp: true et test_vasp_scenario. Consultez le guide des webhooks pour le catalogue complet des événements, la vérification des signatures et la politique de nouvelles tentatives.

Tarifs

Les frais Travel Rule s’élèvent à $0.02 par échange sortant lancé, facturés une seule fois à la création de la transaction, quel que soit le nombre de transitions de statut que l’échange traverse ensuite. Un resend ne les facture jamais à nouveau.
  • Ils s’appliquent à une transaction travelRule sortante sur une application où Travel Rule est activé, dont le montant est égal ou supérieur à votre threshold_amount, de sorte que Didit lance pour elle un échange géré. Un transfert vers un portefeuille auto-hébergé, ou vers le portefeuille vérifié de votre utilisateur, compte aussi : la preuve de propriété constitue son échange.
  • Toute autre transaction sortante qui contient des données Travel Rule est facturée comme Transaction Screening standard, inclus dans votre forfait jusqu’à son quota mensuel de transactions, puis à $0.001 au-delà. Sont concernés : un transfert sous votre seuil, un paiement Lightning auquel la Travel Rule ne s’applique pas, et un transfert enregistré passivement lorsque Travel Rule est désactivé. Une transaction est facturée sur l’une des deux lignes, jamais sur les deux.
  • Les transactions travelRule entrantes ne donnent lieu à aucuns frais Travel Rule ni de surveillance : c’est le cas du tronçon entrant que Didit crée sur l’application du bénéficiaire via le rail INTERNAL, des demandes TRP entrantes et des enregistrements après dépôt.
  • Une application qui a rejoint Travel Rule gratuitement ne paie aucun des deux frais sur les transactions travelRule sortantes tant qu’elle n’active pas la facturation sortante avec POST /v3/travel-rule/workspace/billing/. La réception reste gratuite.
  • Les applications sandbox ne sont jamais facturées.
  • Les frais Travel Rule sont affichés à $0.02, et Transaction Screening à $0.001 au-delà de votre quota. Chaque transaction est facturée au prix de la ligne qu’elle utilise.
  • Si le filtrage crypto AML/portefeuille s’exécute aussi sur la même transaction (par exemple un dépôt entrant avec currency_kind: "crypto" et la surveillance activée), il est facturé séparément au tarif standard Wallet Verification & Screening. Il n’est pas inclus dans les frais Travel Rule.
  • Le filtrage de la contrepartie est optionnel et facturé séparément, au prix Company AML Screening par filtrage. Un filtrage de moins de 30 jours est réutilisé sans frais. Les décisions, les questionnaires et l’évaluation de vigilance de l’annuaire ne coûtent rien.

Tarifs de l’attribution

L’attribution par analyse blockchain nécessite une seule recherche sur le portefeuille auprès du fournisseur d’analyse, et vous obtenez toujours la valeur complète de cette recherche :
  • La surveillance crypto a déjà filtré le portefeuille de cette transaction : le résultat enregistré est réutilisé. Un appel d’analyse, une seule facturation Wallet Verification & Screening (celle déjà effectuée par la surveillance crypto). L’attribution n’ajoute rien.
  • La surveillance crypto n’a pas été exécutée : Didit effectue une seule fois l’appel d’analyse requis et transmet sa valeur complète. La transaction est facturée au tarif standard Wallet Verification & Screening ($0.15 avec l’analyse gérée par Didit) et reçoit en retour l’intégralité du résultat du filtrage crypto (score de risque, catégorie de risque du portefeuille, instantané du réseau), et pas seulement le nom du VASP. La ligne de consommation porte le déclencheur travel_rule_attribution, afin que la facturation soit explicable.
  • Votre propre clé d’analyse : les mêmes frais de plateforme que pour la surveillance crypto avec votre propre clé s’appliquent ici ($0.02 par appel). Votre fournisseur vous facture directement la recherche sous-jacente.
  • Les recherches en échec ne sont jamais facturées. Les renvois réutilisent l’attribution enregistrée, sans nouvel appel ni nouvelle facturation, et les applications sandbox ne sont jamais facturées.
Un transfert sortant qui a nécessité une attribution coûte donc $0.02 (Travel Rule) plus une facturation Wallet Verification & Screening. Lorsque les deux produits sont activés, l’appel d’analyse et sa facturation n’ont lieu qu’une seule fois.

Ensemble de règles EU TFR

Quatre règles prédéfinies sont incluses dans l’ensemble travel_rule_eu_tfr, conçues spécifiquement pour les obligations du règlement EU TFR. Comme tout autre ensemble de règles prédéfinies, activez-le depuis la Business Console sous Transactions → Rules → Library. Ces règles se déclenchent à chaque transition pertinente : à la création du transfert, lors de la confirmation de propriété, lors de la réponse à la récupération par e-mail et via le processus de traitement des expirations. Ainsi, un transfert qui ne se résout jamais reste signalé pour examen de conformité au lieu de se régler sans bruit.

Pourquoi cela fonctionne mieux sur une seule plateforme

La conformité Travel Rule est un problème d’identité déguisé en problème de paiement, et Didit détient déjà l’identité :
  • Données vérifiées à portée de main. La personne concernée par chaque transfert est reliée par vendor_data au profil KYC que Didit a vérifié lors de l’onboarding (OCR du document, détection du vivant, correspondance faciale). Vous pouvez donc construire originator_data à partir de champs vérifiés, sur la même plateforme. Pour un utilisateur dont la vérification KYC est approuvée, Didit transmet l’identité vérifiée et non les valeurs que vous avez saisies (voir Données vérifiées et provenance).
  • Biométrie renforcée pour les transferts à risque. Une règle peut mettre en attente tout transfert de montant élevé jusqu’à ce que l’utilisateur réussisse une revérification biométrique par rapport au visage capturé lors de l’onboarding. C’est la meilleure réponse possible à la prise de contrôle de compte au moment du retrait. Voir l’exemple détaillé.
  • Un seul graphe d’appareils. Les transferts soumis via le SDK portent la même analyse de l’appareil que les sessions KYC. Ainsi, « nouvel appareil + montant élevé + bénéficiaire pour la première fois » constitue une seule règle, et non une jointure dans un entrepôt de données.
  • Filtrage, règles, dossiers et échange au même endroit. Le filtrage des portefeuilles qui identifie le VASP de la contrepartie est le même filtrage que vos règles et vos analystes consultent : une seule facturation, une seule console, un seul journal d’audit.
  • Tarifs publiés. $0.02 par échange sortant, sans paliers de volume, sans appel commercial.

FAQ

Comment fonctionne l’échange

Didit exécute l’échange lui-même. Lorsque vous soumettez un transfert travelRule, Didit effectue une découverte de contrepartie en temps réel, envoie les données de l’initiateur et du bénéficiaire au VASP destinataire via les canaux qui les transportent, et ne marque le transfert COMPLETED qu’une fois que la contrepartie le confirme : il s’agit d’une poignée de main gérée, et non d’un journal post-règlement. Une réponse de la contrepartie est traitée sur tous les canaux sauf TRUST, qui ne fait qu’envoyer ; un transfert passant par lui expire donc au lieu d’aboutir. Sur GTR, VERIFYVASP et SYGNA, Didit lit la réponse dans la réponse du réseau ou l’interroge toutes les 5 minutes, et aucun échange sur ces canaux n’a encore abouti sur un réseau en production ; sur TRP, une réponse que la contrepartie ne donne pas immédiatement nécessite l’hôte TRP public de Didit, qui n’est pas encore en service (voir Réseaux Travel Rule). Didit conserve aussi l’enregistrement complet et le statut de conformité de chaque échange (un cycle de vie à 13 états, des webhooks et une piste d’audit). Si vous préférez gérer Travel Rule via votre propre processus, Didit peut à la place enregistrer les données de façon passive comme votre système de référence unique (voir la question sur l’enregistrement passif ci-dessous). Les deux modes sont disponibles : l’échange actif s’exécute dès que vous définissez is_enabled: true dans les paramètres (la valeur par défaut est false) ; jusque-là, Didit enregistre les données de façon passive.
Il est natif. Didit exécute sa propre implémentation du Travel Rule Protocol (TRP), servie sur trp.didit.me une fois que cet hôte public sera en service (il ne répond pas encore, donc les contreparties ne peuvent pas vous joindre via TRP aujourd’hui), et implémente directement les protocoles des réseaux du secteur - GTR (Global Travel Rule), TRUST, VerifyVASP et Sygna Bridge (pris en charge nativement, y compris l’IVMS 101 chiffré en ECIES de Sygna et les demandes d’autorisation signées). Didit ne revend pas une couche d’interopérabilité tierce unique. Atteindre une contrepartie présente sur un réseau d’adhésion spécifique passe nécessairement par ce réseau - il n’existe pas de canal universel unique dans le Travel Rule - mais la découverte, la poignée de main, la vérification et la tenue des registres sont tous exécutés par Didit. Aujourd’hui, chaque canal de réseau nécessite votre propre adhésion, et TRUST ne fait qu’envoyer : voir Vue d’ensemble.
Implémentés nativement, essayés dans un ordre de priorité strict : INTERNAL (les deux VASP sur Didit), TRP (OpenVASP Travel Rule Protocol), GTR, TRUST, VerifyVASP, Sygna Bridge, et un repli EMAIL pour les contreparties sans outillage. Les canaux de réseau nécessitent aujourd’hui votre propre adhésion, TRUST ne fait qu’envoyer, et la réception via un réseau n’est pas encore activée : voir Réseaux Travel Rule. CODE, TRISA, Notabene et Veriscope apparaissent dans la Marketplace de Didit sous forme de vignettes de demande d’accès, sans intégration pour l’instant. Voir Comment Didit achemine un échange pour la table de routage complète, et Réseaux Travel Rule pour savoir quelles contreparties chacun permet d’atteindre aujourd’hui.
TRP est la base que nous recommandons : il ne nécessite aucune adhésion partagée - partager votre Travel Address permet à tout VASP qui parle TRP de vous joindre - et lorsqu’une contrepartie n’est connue que par son adresse, l’attribution par analyse blockchain ne l’achemine via TRP que si l’entrée d’annuaire attribuée possède déjà un endpoint TRP. En sortie, Didit envoie via ce canal les actifs listés sous identifiants d’actifs TRP, et une contrepartie qui ne répond pas immédiatement nécessite l’hôte TRP public de Didit, qui n’est pas encore en service. Pour les réseaux d’adhésion, choisissez selon l’endroit où opèrent vos contreparties : GTR offre une large couverture mondiale, TRUST est orienté vers les VASP américains, et VerifyVASP (qui exploite désormais aussi Sygna Bridge) a une forte couverture en Asie. Oui, aujourd’hui vous devez disposer de votre propre adhésion : Didit ne détient encore aucune adhésion de plateforme sur ces réseaux et est en cours d’onboarding en tant que fournisseur. Connectez votre adhésion dans Marketplace ; vous n’êtes pas limité à un seul réseau. N’oubliez pas que TRUST ne fait qu’envoyer, de sorte qu’un transfert passant par lui n’aboutit pas, et qu’aucun échange sur un réseau n’a encore abouti sur un réseau en production.
Via une chaîne de résolution : un autre client Didit ayant prouvé le contrôle du portefeuille (le canal INTERNAL), votre propre carnet d’adresses pour une Travel Address enregistrée, puis les canaux de protocole et de réseau, puis l’attribution par analyse blockchain. Si le transfert ne porte qu’un portefeuille de destination, Didit le résout avec des données d’analyse blockchain pour attribuer l’échange ou le service de conservation de la contrepartie et le répertorie dans l’annuaire des VASP. Le transfert n’est alors routé automatiquement que si cette entrée d’annuaire dispose déjà d’un itinéraire, comme un endpoint TRP ; sinon il se termine avec COUNTERPARTY_VASP_NOT_FOUND, avec le nom de la contrepartie enregistré, et vous pouvez le renvoyer une fois qu’un itinéraire existe, par exemple après avoir enregistré le travel_address de la contrepartie dans votre entrée de carnet d’adresses pour ce portefeuille (pour les actifs listés sous identifiants d’actifs TRP).

Tarifs et licences

Travel Rule est un produit distinct, facturé à l’usage. Il n’est pas inclus dans la vérification d’identité. Il coûte **$0.02 par échange sortant lancé** ([tarifs publiés](#tarifs), sans niveaux de volume ni appel commercial). Lorsque le routage nécessite une recherche d'analyse blockchain pour identifier le VASP de la contrepartie, cette recherche est facturée une fois au [tarif d'attribution](#tarifs-de-l’attribution) : à partir de $0.15 en mode géré, ou $0.02 avec une clé de fournisseur existante. Elle n’est pas refacturée si la surveillance crypto a déjà effectué le filtrage du même portefeuille. Un transfert en dessous de votre seuil, ou un transfert enregistré alors que Travel Rule est désactivé, est facturé comme filtrage des transactions ; les transferts entrants, comme les transactions que Didit crée côté bénéficiaire, ne sont pas soumis aux frais Travel Rule.
Non. Une transaction travelRule est facturée sur une seule ligne : les frais Travel Rule ($0.02) lorsqu’elle lance un échange sortant, sinon le filtrage des transactions standard. La recherche d’analyse blockchain est facturée une fois : si la surveillance crypto a déjà effectué le filtrage du portefeuille de destination de la transaction, l’attribution réutilise ce résultat stocké sans coût supplémentaire. Les transactions entrantes créées par Didit côté bénéficiaire ne sont jamais soumises aux frais Travel Rule.
Oui. La surveillance KYT et des transactions de Didit, le filtrage AML et des sanctions, l’analyse de risque des portefeuilles par analyse blockchain et les capacités Travel Rule sont toutes documentées publiquement sur docs.didit.me, et vous êtes libre de citer ces pages dans un dossier réglementaire. Pour une déclaration formelle de capacités du produit adaptée à un dépôt précis - par exemple une demande de licence VASP au titre de l’AIFC/AFSA ou de MiCA, ou une autre demande de licence VASP - contactez votre équipe de compte Didit avec le format exigé par votre régulateur.

Opérations et données

Ils aboutissent immédiatement à NOT_APPLICABLE : aucun rail n’est sollicité et aucune donnée d’identité ne quitte Didit. La transaction est facturée comme un filtrage standard des transactions, et non au tarif Travel Rule. Didit compare le seuil au montant converti dans la devise par défaut de votre application. Si Didit ne peut pas convertir le montant, le seuil n’est pas appliqué et le transfert est échangé. Le règlement TFR de l’UE ne prévoit aucun minimum : les CASP de l’UE doivent donc conserver threshold_amount à 0.00.
Le transfert reste en AWAITING_COUNTERPARTY jusqu’à l’expiration de confirmation_timeout_hours. Un processus automatique le fait alors passer à EXPIRED et applique votre timeout_outcome à la transaction sous-jacente : HOLD pour examen, REJECT pour le refuser, ou PROCEED pour la laisser inchangée. La règle prédéfinie tr_eu_expired_review signale en outre chaque expiration à vos analystes. Une contrepartie qui ne prend jamais en charge le message est un cas différent : Didit réessaie, et après la sixième tentative échouée, le transfert passe à COUNTERPARTY_VASP_NOT_REACHABLE (voir Livraison et nouvelles tentatives).
Oui : c’est le rail EMAIL. Le contact conformité de la contrepartie reçoit un lien de récupération à usage unique et peut confirmer ou refuser l’échange depuis une page hébergée, sans compte Didit, sans clé API et sans intégration. Pour les dépôts déjà réglés avant qu’un échange ne soit possible, utilisez l’inscription sunrise après le dépôt.
Déclarez la destination comme auto-hébergée (payment_method.method_type: "unhosted_wallet"). Didit ne passe pas par le routage VASP et recueille plutôt la preuve que votre client contrôle le portefeuille, par signature de message, test de Satoshi, capture d’écran ou auto-déclaration, via le widget de vérification de propriété du portefeuille. Il s’agit de l’obligation du TFR de l’UE relative aux portefeuilles auto-hébergés, sous forme d’API.
Oui. Tant que la propriété du portefeuille n’est pas confirmée de votre côté, les données personnelles de la contrepartie d’un transfert entrant sont masquées, dans l’objet du transfert comme dans la partie contrepartie de la transaction (voir Données de contrepartie masquées). Les pages de récupération par e-mail n’affichent que le résumé du transfert (nom du VASP donneur d’ordre, montant, portefeuille de destination), jamais les données des personnes. Les échanges sur les rails s’effectuent en HTTPS, et les rails de réseau (GTR, TRUST, VERIFYVASP, SYGNA) chiffrent en outre le payload IVMS avec la clé publique du VASP destinataire.
Oui. Lorsque Travel Rule est désactivé, tout travel_rule_details que vous envoyez (y compris un status fourni par l’appelant, actuel ou hérité) est enregistré passivement sur la transaction : aucun rail n’est sollicité et rien n’est négocié. Utilisez cette option pour compléter un historique ou faire tourner votre propre processus tout en conservant un seul système de référence.
Utilisez une application sandbox avec Travel Rule activé. Le sandbox ignore les règles, la facturation et les webhooks transaction.created et transaction.status.updated. Chaque échange sortant est dirigé vers le VASP de test de Didit sur le rail TEST_VASP, qui répond au sein de Didit selon le scénario choisi : acceptation, refus, non-concordance du nom, injoignable, délai dépassé ou auto-hébergé. Vous recevez des webhooks travel_rule.status.updated marqués environment: sandbox, et aucun message n’atteint une véritable contrepartie. Pour les transferts entrants, faites envoyer par le VASP de test un transfert avec Déclencher un transfert entrant de test du VASP de test, ou enregistrez-en un avec POST /v3/travel-rule/inbound/. Dans les deux cas, la décision est prise à partir de votre carnet d’adresses de portefeuilles : ON_HOLD, UNCONFIRMED_OWNERSHIP, COUNTERPARTY_MISMATCHED_DATA, ou COMPLETED puis FINISHED. Pour un échange à deux parties avec une autre application réelle, utilisez deux de vos applications en production sur le rail INTERNAL : l’application destinataire doit être détectable et prouver le contrôle du portefeuille de destination par un message signé ou un dépôt de test.

Prochaines étapes

Widget de vérification de la propriété du portefeuille

Prouvez qu’un client contrôle un portefeuille par signature, test de Satoshi, capture d’écran ou auto-déclaration.

Interopérabilité

Les endpoints destinés aux contreparties que les autres VASP utilisent pour vous joindre : TRP, rappels, récupération par e-mail, sunrise.

Réseaux

Ce que chaque rail et chaque réseau permet d’atteindre aujourd’hui, et ce qui nécessite encore une adhésion.

Tester en sandbox

Testez chaque résultat possible avec le VASP de test de Didit avant la mise en production.

Soumettre des transactions

Comment inclure les données Travel Rule dans le payload de Create Transaction.

Bibliothèque de règles

Le catalogue complet des règles prédéfinies, y compris le lot Travel Rule.

Webhooks

Abonnez-vous à travel_rule.status.updated et à toutes les autres familles d’événements.

Guide de la console

Consulter le statut Travel Rule dans la vue de détail de la transaction.
Dernière modification le 7 novembre 2026