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
COMPLETEDque 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.
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.
/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 appelPOST /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 :
- Validation et rattachement du sujet. La charge utile est validée.
Un
transaction_iddéjà utilisé dans votre application est rejeté avec400, et lorsque deux requêtes portant le même identifiant arrivent en même temps, l’une d’elles reçoit409. Lesubjectest rattaché à votre profil utilisateur ou entreprise viavendor_data, de sorte que le transfert apparaît sur ce profil pour la surveillance. Un sujet ou unvendor_datade contrepartie figurant sur une liste de blocage est rejeté avec400, 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. - 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.
- 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.
- 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_APPLICABLEsous votre seuil,UNCONFIRMED_OWNERSHIPpour un portefeuille auto-hébergé déclaré,COMPLETEDlorsque l’autre portefeuille est votre portefeuille vérifié, etAWAITING_COUNTERPARTYdans les autres cas, avec un rail encore vide. Chaque changement de statut met en file d’attente un événementtravel_rule.status.updated, et Didit l’envoie après letransaction.createdde la transaction (étape 7). - 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_verificationest activé, une session du widget de preuve de propriété du portefeuille est créée et renvoyée dansaction_required. - É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). - 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.createdest envoyé à vos destinations de webhook, suivi des événementstravel_rule.status.updatedde l’étape 4. - 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_COUNTERPARTYsur ce rail tant que la contrepartie ne répond pas, est réglé immédiatement lorsqu’une contrepartieINTERNALcorrespondante est trouvée (COMPLETEDouCOUNTERPARTY_MISMATCHED_DATA), ou passe àCOUNTERPARTY_VASP_NOT_FOUNDlorsqu’aucun rail n’atteint la contrepartie. Chaque changement déclenchetravel_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 enCOUNTERPARTY_VASP_NOT_FOUNDreçoit à ce moment sa session de widget de preuve de propriété du portefeuille (lorsqueauto_wallet_verificationest activé). Vous la lisez dansaction_requiredde la transaction avec Get transaction.
Comment chaque étape correspond à vos obligations
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 unstatus 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).
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 dansdelivery, 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.
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 avecGET/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 :
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_birthetplace_of_birth. Les entreprises utilisent les cinq premiers. nameetwallet_addresssont 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
PUTfusionne par type d’entité : l’envoi deindividuallaisse le choix enregistré pourcompanyinchangé. - 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é avec400. Les erreurs figurent sousdata_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 :
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 :
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_dataest 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 leoriginator_dataque 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 canalINTERNAL, l’application du bénéficiaire reçoit aussi lesubjectque 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 avecPOST /v3/travel-rule/inbound/lorsque leholder_vendor_datadu portefeuille correspondant désigne cet utilisateur.
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 :
*_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 à platoriginator_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
originatoret un blocbeneficiary, avecoriginatorPersonsoubeneficiaryPersonscontenant unnaturalPersonou unlegalPerson, ainsi queaccountNumber; - les mêmes blocs à l’intérieur d’un objet
ivms101(ouIVMS101) ; - une personne IVMS 101, ou un bloc complet, en tant que
originator_dataoubeneficiary_data.
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é.
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épond400. 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."
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’endpointTRP 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 :
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
travelRuledepuis 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
INTERNALentre 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.
Envoyer un transfert
Incluez le transfert dansPOST /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 :
- Si le montant est inférieur au
threshold_amountconfiguré, le transfert passe immédiatement àNOT_APPLICABLEet aucune négociation n’a lieu. - 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_OWNERSHIPet le widget de propriété du portefeuille est généré automatiquement (voir Portefeuilles auto-hébergés déclarés). - 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 pourbeneficiary_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é etis_discoverabledéfini, et correspond au VASP que vous avez indiqué lors de l’envoi decounterparty_vasp_leioucounterparty_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 parINTERNAL.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 contientbeneficiary_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 (parcounterparty_vasp_leioucounterparty_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), doncdisabled_networksne modifie pas cette étape aujourd’hui.EMAIL:beneficiary_data.counterparty_vasp_emaila été fourni, oucounterparty_vasp_namese résout en une entrée de l’annuaire avec un e-mail de conformité.
- Si aucun canal ne parvient à résoudre une contrepartie, Didit exécute la chaîne de découverte de la contrepartie. Lorsque
vasp_attribution_enabledest 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 dansbeneficiary_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. - 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 (lorsqueauto_wallet_verificationest 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 avecaction: "resend"une fois que vous disposez de meilleures données sur la contrepartie.
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 :
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 :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 :
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 detravel_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 souschain_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 soustravel_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_addresslorsque 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).
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 commepayment_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 :
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.
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 avecPATCH /v3/transactions/{transaction_id}/travel-rule/ :
- Transferts sortants uniquement, et jamais un transfert
CANCELLEDouEXPIRED(409). - Lorsque les entrées UTXO du transfert sont connues, le portefeuille doit en faire partie (sinon
400). Envoyez aussimemosur un réseau à mémo. - Seul l’enregistrement est modifié : rien n’est envoyé à nouveau à la contrepartie et le statut reste inchangé.
chain_details.originatordevient le nouveau portefeuille avecsource: updated, latimelineenregistre l’ancien et le nouveau portefeuille, et la contrepartieINTERNALdu 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 unchain (voir Champs de résolution de la contrepartie) ; sans chaîne, elle est ignorée.
Lorsque le portefeuille est attribué à une entité connue, Didit :
- Enregistre l’attribution sur le transfert :
beneficiary_data.counterparty_vasp_nameest renseigné (si vous ne l’avez pas fourni), et les métadonnées du transfert contiennent un blocvasp_attributionavec le nom de l’entité, son type, son pays etsource: "blockchain_analytics". - Ajoute l’entité à l’annuaire VASP (ou rapproche l’entrée existante), afin que l’attribution reste stable d’un transfert à l’autre.
- 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.
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 :
originator_datacorrespond à 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 railEMAILn’affiche au destinataire que les champs du donneur d’ordre que vous partagez dansdata_sharing_fields.beneficiary_data.chainvautETHcar la requête n’indiquait aucune chaîne ; Didit l’a déduite de la devise.statusvautAPPROVEDcar 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.
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.COMPLETED une fois qu’il a été diffusé on-chain :
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.
{"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 enCOUNTERPARTY_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 :
reasonest la cause lisible par la machine,""pour tout autre statut.next_actionsliste les actions qui font avancer le transfert,[]pour tout autre statut.deliveryest le message le plus récent que le transfert doit envoyer à l’autre partie :kind,status(QUEUED,SENDING,RETRYING,DELIVEREDouFAILED),rail,attempts,last_error,next_attempt_atetdelivered_at, ounulllorsqu’il n’en doit aucun.discoveryindique où Didit a cherché la contrepartie, avec une entrée par étape exécutée :step,outcome(match,missouskipped),reason,vasp,railetat.
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.
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 :
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 surunhosted_wallet.
- 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_verificationest 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 blocaction_requiredde 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_DATAsi la correspondance échoue).
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 enUNCONFIRMED_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.
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_HOLDsur un transfert entrant.UNCONFIRMED_OWNERSHIP,ON_HOLDouCOUNTERPARTY_MISMATCHED_DATAsur un transfert entrantTRP, afin qu’un responsable de la conformité puisse traiter manuellement une demande de la contrepartie.
INTERNAL, enregistrement sunrise ou portefeuille auto-hébergé déclaré) :
confirmed: truemarque l’entrée correspondante du carnet d’adresses de portefeuilles comme propriété vérifiée, puis lance la correspondance de nom avec leholder_namede cette entrée :COMPLETEDsi elle réussit,COUNTERPARTY_MISMATCHED_DATAsinon. La contrepartie sur le railINTERNAL, lorsqu’elle existe, reçoit le même résultat. Pour un portefeuille auto-hébergé déclaré,confirmed: trueenregistre 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: falsefait 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_HOLDentrant sans entrée correspondante crée l’entrée à partir debeneficiary_data.wallet_addressdu 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 renvoie409avec{"detail": "wallet address entry no longer available"}, ce qui est aussi la réponse pour une entrée supprimée.
TRP, le même appel répond à la demande de la contrepartie :
confirmed: truenécessite une adresse de paiement à renvoyer, prise dans lewallet_addressde la requête, puis dans l’entrée correspondante, puis dansbeneficiary_data.wallet_address. Si aucune des trois n’est disponible, la requête renvoie400avec{"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éfinitownership_confirmedet fait passer le transfert àCOMPLETED.confirmed: falseenvoie 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
502et 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.
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).
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 viaGTR, 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
GTRetSYGNA. Un transfert qui attend votre décision (UNCONFIRMED_OWNERSHIP,ON_HOLDouCOUNTERPARTY_MISMATCHED_DATA) se décide avecPOST .../travel-rule/ownership/:confirmed: truel’approuve (transmettezwallet_addresslorsque le transfert n’en désigne aucun) etconfirmed: falsele refuse. Didit répond au réseau après la réponse, avec les mêmes nouvelles tentatives. Un transfert déjà traité renvoie409. - 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 virementCOUNTERPARTY_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 :
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é portename_match, et l’entrée de chronologie qui a tranché porte le même objet à côté de son detail :
scoreest la similarité des deux noms complets, de 0 à 100, et vaut0lorsque l’un des deux est vide.matchedest le résultat au niveaustrictness, de sorte que les deux peuvent diverger :FUZZYtranche sur son propre ratio de similarité de0.85.matched_namessont les mots du nom du virement retrouvés dans l’autre nom ;mismatched_namessont les mots que l’un des deux côtés possède et que l’autre n’a pas.methodvautnone,exact_token,token_subset,token_containmentousequence_ratio, pourNONE,STRICT,DEFAULT,CONTAINSetFUZZY.compared_withvautwallet_holder(le titulaire enregistré pour le portefeuille) oucounterparty_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 êtreCOUNTERPARTY_MISMATCHED_DATAalors quename_match.matchedvauttrue. name_matchfigure dans l’objet virement renvoyé par les endpoints Travel Rule et danstravel_ruleintégré à la transaction, et vautnulltant 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 :
kindvautMESSAGE(par défaut) ouDOCUMENT_REQUEST. Une demande de document exige au moins une entrée dansrequested_documents(jusqu’à 10) ; un message exige unbody(jusqu’à 5000 caractères) ou un fichier.reply_todé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, etfulfilled_aty est renseigné. Votre propre réponse ne satisfait jamais votre propre demande.- Envoyez en multipart avec
filespour 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 renvoie400surfiles(« 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
INTERNALetEMAIL. SurINTERNAL, l’autre client Didit voit le message de son côté de la paire. SurEMAIL, 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 renvoie201. - La requête renvoie
409, avec une raison dansdetail, pour un virement surTRP,GTR,TRUST,VerifyVASPouSygna(ces protocoles ne transportent pas de messages), un virement sans contrepartie pour l’instant, un virementINTERNALdont les données ne correspondaient pas au titulaire du portefeuille (rien n’a atteint le destinataire), un virementINTERNALdont 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 virementEMAILdont la contrepartie n’a plus d’adresse e-mail de conformité, et un virementEMAILdont 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 (canalINTERNAL, 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.
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 avecPOST /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.
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 surEMAIL : 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_emailque 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 lacounterparty_vasp_emailque 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.
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 enAWAITING_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 :
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 avecPOST /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.
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 avecPOST .../travel-rule/ownership/:confirmed: trueajoute 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_namede l’entrée ->COUNTERPARTY_MISMATCHED_DATA. - Entrée vérifiée et nom correspondant ->
COMPLETED, puis immédiatementFINISHED(le dépôt a déjà été réglé).
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 deUNCONFIRMED_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 :
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 :
TRPlorsque l’entrée comporte un point de terminaison TRP vérifié par l’annuaire.GTR,TRUST,VERIFYVASP,SYGNAlorsqu’un réseau répertorie le VASP sous un code. La route n’estavailableque si votre application peut envoyer via ce réseau aujourd’hui (via: own_membershippour votre propre adhésion connectée,via: didit_membershippour celle de Didit). Sinon, elle estunavailableavecreason: no_membershipoureason: 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.EMAILlorsqu’il existe un contact de conformité pour l’entrée.INTERNALpour un membre du réseau Didit.
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 dontentities 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 aveclimitetoffset. Filtrez avec?counterparty=et l’idd’une entrée de l’annuaire ; une valeur qui n’est pas un UUID renvoie400.POSTajoute un contact privé (counterparty,email,namefacultatif) et renvoie201. Ajouter une adresse que l’entrée possède déjà parmi vos propres contacts renvoie ce contact. Une entrée inconnue renvoie400("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 renvoient404.
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.
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_discoverablevauttrueetlegal_nameest renseigné ;compliance_emailest renseigné et confirmé : envoyez la confirmation avecPOST /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.
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 parcounterparty_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.
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 :
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.
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 :
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_DECLINEouCOUNTERPARTY_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
CANCELLEDrelève de l’initiative de l’expéditeur,COUNTERPARTY_VASP_NOT_REACHABLEpeut être un échec du côté de l’expéditeur, et une requête encore en cours n’est pas encore décidée.
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 avecPUT /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é. GETrenvoie la décision applicable, ou{"decision": null}.DELETEsupprime votre propre décision et renvoie celle qui s’applique ensuite : la décision de la marque pour une entité, sinonnull. Il répond404lorsque votre application n’a aucune décision propre sur la contrepartie.reasonest facultatif, limité à 255 caractères, et chaquePUTle remplace.decided_byest 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 aveccounterparty_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 ceprice,fresh_dayset si le filtrage estenabled, sans frais. Un filtrage datant de moins de 30 jours est considéré comme récent :POSTle renvoie à nouveau avec200etoutcome: reusedsans rien facturer, sauf si vous envoyezrefresh: true. Un nouveau filtrage répond201avecoutcome: 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.
statusvautApprovedlorsqu’aucune correspondance possible ne demande d’attention, etIn ReviewouDeclinedpour une correspondance possible avec des sanctions ou de la presse négative.flaggedvauttruetant qu’un filtrage récent estIn ReviewouDeclined. - Effet. Tant que votre dernier filtrage de la contrepartie est signalé et que le filtrage reste activé,
counterparty_risk_levelvautHIGHdans vos règles, de sorte que le préréglagetravel-rule-counterparty-high-risk, une fois installé, met en attente ses transferts. Lerisk_levelpartagé 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.
409lorsque le filtrage est désactivé, pour une application sandbox, ou lorsqu’un filtrage de la même contrepartie est déjà en cours.403aveccode: insufficient_creditslorsque votre solde ne couvre pas le prix.502lorsque 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_todoit ê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 danscontacts; 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épond400lorsque 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/Alorsqu’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), etcurrentvauttruejusque-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 à
EXPIREDet son lien cesse de fonctionner. Un seul e-mail par minute peut être envoyé à une adresse ; un envoi plus rapide répond429. - 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, avecanswersindexées par identifiant de question une fois répondues. Le répertoire les affiche sousquestionnaires. Elles sont privées pour votre application.
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élaiconfirmation_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é :
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 :
sunrise_policy, lorsqu’elle est définie et que la juridiction de la contrepartie figure danssunrise_jurisdictions.non_responsive_policy, lorsqu’elle est définie et que la contrepartie estUNRESPONSIVE.timeout_outcomedans 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 échangeINTERNAL, le client Didit qui l’a reçu. Chaque champ est vide lorsque l’échange n’a atteint aucune contrepartie connue.
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 :
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. Unresend ne les facture jamais à nouveau.
- Ils s’appliquent à une transaction
travelRulesortante sur une application où Travel Rule est activé, dont le montant est égal ou supérieur à votrethreshold_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
travelRuleentrantes 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 railINTERNAL, des demandesTRPentrantes 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
travelRulesortantes tant qu’elle n’active pas la facturation sortante avecPOST /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.
Ensemble de règles EU TFR
Quatre règles prédéfinies sont incluses dans l’ensembletravel_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.
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_dataau profil KYC que Didit a vérifié lors de l’onboarding (OCR du document, détection du vivant, correspondance faciale). Vous pouvez donc construireoriginator_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-t-il lui-même l'échange VASP à VASP, ou se contente-t-il d'enregistrer les données Travel Rule après qu'un autre réseau a terminé la poignée de main ?
Didit exécute-t-il lui-même l'échange VASP à VASP, ou se contente-t-il d'enregistrer les données Travel Rule après qu'un autre réseau a terminé la poignée de main ?
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.L'échange est-il natif chez Didit, ou dépend-il d'un fournisseur d'interopérabilité externe (Notabene, Sygna, VerifyVASP, TRP) ?
L'échange est-il natif chez Didit, ou dépend-il d'un fournisseur d'interopérabilité externe (Notabene, Sygna, VerifyVASP, TRP) ?
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.Quels réseaux et protocoles Travel Rule Didit prend-il en charge ?
Quels réseaux et protocoles Travel Rule Didit prend-il en charge ?
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.Quel réseau dois-je utiliser, et ai-je besoin de mes propres adhésions ?
Quel réseau dois-je utiliser, et ai-je besoin de mes propres adhésions ?
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.Comment Didit trouve-t-il le VASP de la contrepartie lorsque je n'ai qu'une adresse ?
Comment Didit trouve-t-il le VASP de la contrepartie lorsque je n'ai qu'une adresse ?
Tarifs et licences
Combien coûte Travel Rule, et est-il inclus dans mon forfait KYC ou de vérification ?
Combien coûte Travel Rule, et est-il inclus dans mon forfait KYC ou de vérification ?
Serai-je facturé deux fois lorsque Travel Rule et la surveillance crypto fonctionnent tous deux ?
Serai-je facturé deux fois lorsque Travel Rule et la surveillance crypto fonctionnent tous deux ?
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.Didit peut-il fournir une documentation ou une déclaration de capacités pour mon dossier de licence VASP ou auprès du régulateur ?
Didit peut-il fournir une documentation ou une déclaration de capacités pour mon dossier de licence VASP ou auprès du régulateur ?
Opérations et données
Que se passe-t-il pour les transferts inférieurs à mon seuil ?
Que se passe-t-il pour les transferts inférieurs à mon seuil ?
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.Que se passe-t-il si la contrepartie ne répond jamais ?
Que se passe-t-il si la contrepartie ne répond jamais ?
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).Ma contrepartie ne dispose d'aucun outil Travel Rule. Puis-je tout de même être en conformité ?
Ma contrepartie ne dispose d'aucun outil Travel Rule. Puis-je tout de même être en conformité ?
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.Comment fonctionnent les retraits vers le portefeuille propre de l'utilisateur ?
Comment fonctionnent les retraits vers le portefeuille propre de l'utilisateur ?
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.Les données personnelles de la contrepartie sont-elles protégées pendant l'échange ?
Les données personnelles de la contrepartie sont-elles protégées pendant l'échange ?
GTR, TRUST, VERIFYVASP, SYGNA) chiffrent en outre le payload IVMS avec la clé publique du VASP destinataire.Je gère déjà la conformité Travel Rule avec mon propre processus. Puis-je simplement l'enregistrer ?
Je gère déjà la conformité Travel Rule avec mon propre processus. Puis-je simplement l'enregistrer ?
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.Comment tester avant la mise en production ?
Comment tester avant la mise en production ?
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.
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 endpointTRP; sinon il se termine avecCOUNTERPARTY_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é letravel_addressde la contrepartie dans votre entrée de carnet d’adresses pour ce portefeuille (pour les actifs listés sous identifiants d’actifs TRP).