Validation API

txValidateFull is an API call used for threshold check, wallet identification and data collection.

It is implemented as part of your front-end withdrawal screen and triggered after your customer inputs:

  1. the destination wallet address;
  2. the virtual asset type, and;
  3. the amount.

To do the threshold check, we call Coingeko with the asset type and the amount. They will then return the FIAT value of the transaction which we then compare against the De Minimis Threshold of your jurisdiction. The travel rule flow only needs to be triggered if the value is above the threshold.

The wallet identification works by leveraging our internal address books and integrations with different blockchain analytics providers. We first check if the destination address is already known and if not, query blockchain analytics to see if they are able to identify the VASP behind it.

Data collection is enabled by the "errors" that the API can return, showing which details about the beneficiary need to be collected from your customer.

🚧

Token

In order to not expose the accessToken, this call can be done using the customerToken.


Response

The response to txValidateFull contains the following fields:

"isValid": "true" or "false",
"type": "BELOW_THRESHOLD" or "TRAVELRULE" or "NON_CUSTODIAL",
"beneficiaryAddressType": "UNKNOWN" or "HOSTED" or "UNHOSTED",
"addressSource": "ADDRESS_GRAPH" or "NAME_OF_BLOCKCHAIN_ANALYTICS",
"beneficiaryVASPname": "VASP_NAME",
"errors": "MISSING_FIELDS_REQUIRED_BY_YOUR_JURISDICTION"
"warnings": "MISSING_FIELDS_REQUIRED_BY_COUNTERPARTY_JURISDICTION"

"isValid" will tell you if you have collected all the information needed for the travel rule data transfer. Once this field = "true", you can move on to the next step which is to transfer the information collected here to your back-end, add information about your own customer (the originator) and then perform txCreate (covered in stage 2 of your implementation).

"type" will tell you if the virtual asset value converted to FIAT value of the withdrawal request is above (=TRAVELRULE) or below (=BELOW_THRESHOLD) the threshold in your jurisdiction. If it is to an unhosted wallet which does not require travel rule information to be sent and only collected, it will say NON_CUSTODIAL.

TypeDescription
BELOW_THRESHOLDTravel rule is not required.
TRAVELRULETravel rule is required.
NON_CUSTODIALUnhosted wallet so travel rule is not required. However, depending on the jurisdiction, beneficiary name and wallet proof needs to be collected and saved.

📘

No beneficiary VASP details

If the beneficiary VASP isn't automatically identified or manually added to this call, it will assume that the transfer is to an unhosted wallet and the type will be "NON_CUSTODIAL".

"beneficiaryAddressType" will tell you if your blockchain analytics provider or internal address book has been able to identify the wallet address.

"addressSource" will tell you if the address was found in your internal address book or identified by the blockchain analytics provider.

"beneficiaryVASPname" will tell you the name of the VASP that has been identified as the owner of the wallet address. This name is used in a subsequent call to get its DID.

"errors/warnings" will tell you what information about the beneficiary you need to collect from the sender. If the beneficiary VASP is in a jurisdiction that requires more "beneficiary information" to be sent than yours, txValidate will return those as well in:

ErrorsMissing
beneficiaryNameMissingcollect the first and last name of the recipient
beneficiaryAccountNumberMissingthis is the same as the destination address of the transfer
beneficiaryGeographicAddressMissingthis is only required in Canada
beneficiaryCountryOfResidenceMissingbeneficiary..... countryOfResidence
beneficiaryOwnershipProofMissingif there is no beneficiaryVASPdid because it's going to an unhosted wallet, you need to provide this part instead
beneficiaryNationalIdentificationnot required anywhere for now

Initial validation

This is an example of the initial call that you perform immediately after the sender has filled in the value, the type, and the address of the transfer:

{
    "transactionAsset": "ETH",
    "transactionAmount": "1700000000000000000",
    "originatorVASPdid": "{{vaspDID}}",
    "originatorEqualsBeneficiary": false,
    "validatePartyFields": "BENEFICIARY",
    "transactionBlockchainInfo": {
        "origin": "{{$randomBitcoin}}",
        "destination": "{{$randomBitcoin}}"
    }
}

The response to this initial validation step will tell you two key points:

  • If the value of this transaction is above or below the travel rule threshold;
  • If the destination address was identified by blockchain analytics or your address book;

If the address was not automatically identified, you should get your customer to search and select the correct VASP from our directory by querying txSimpleVASPs with the VASP name they provide. This is covered below in this guide.

{
    "isValid": false,
    "type": "NON_CUSTODIAL",
    "beneficiaryAddressType": "UNKNOWN",
    "addressSource": "UNKNOWN",
    "errors": [
        "beneficiaryNameMissing",
        "beneficiaryOwnershipProofMissing"
    ]
}

Regardless of what the response to your initial validation is, the next step should be that you ask your customer to say if they are sending the funds to themselves or not. If they are sending to themselves, you do not need to collect beneficiary information as beneficiary=originator and you should already have all the details you need for travel rule data transfer in your back-end.


Known VASP (address book)

In this request, we are passing a destination address that has been used before:

{
    "transactionAsset": "BTC",
    "transactionAmount": "10000000000000",
    "originatorVASPdid": "{{vaspDID}}",
    "travelRuleBehavior": false,
    "validatePartyFields": "BENEFICIARY",
    "beneficiaryVASPdid": "{{vaspDIDee}}",
    "transactionBlockchainInfo": {
        "origin": "{{$randomBitcoin}}",
        "destination": "36EmADcNB2u9hqtGbUWi83xDoje8VAAVNU"
    }
}

In the response to a known address, we can see the name of the VASP to which we have sent a travel rule message before and they have confirmed that they control the destination address:

{
    "isValid": false,
    "type": "TRAVELRULE",
    "beneficiaryAddressType": "HOSTED",
    "addressSource": "ADDRESS_HASH",
    "beneficiaryVASPdid": "did:ethr:0x049fc13a4f1e79d4d03f082ca96758179a91da29",
    "beneficiaryVASPname": "Notabene VASP EE",
    "errors": [
        "beneficiaryNameMissing",
        "beneficiaryAccountNumberMissing"
    ],
    "warnings": [
        "originatorCustomerIdentificationMissing",
        "originatorCustomerIdentificationMissing"
    ]
}

Since your address book identified the VASP, you can pass it as beneficiaryVASPdid in txCreate when you have collected the remaining information so that isValid=true.


Known VASP (blockchain analytics)

In this call, we are using an address that we have never sent funds to before, but we are able to get the VASP name from our integration with your blockchain analytics provider:

{
    "transactionAsset": "BTC",
    "transactionAmount": "1700000000000000000",
    "originatorVASPdid": "{{vaspDID}}",
    "travelRuleBehavior": false,
    "validatePartyFields": "BENEFICIARY",
    "transactionBlockchainInfo": {
        "origin": "{{$randomBitcoin}}",
        "destination": "3Nz9UhLoNmUPPmddnfgPsbMZpaHGJwwNZB"
    }
}

In the response, we can see the name of the VASP which has been identified as the owner of the address:

{
    "isValid": false,
    "type": "TRAVELRULE",
    "beneficiaryAddressType": "HOSTED",
    "addressSource": "CHAINALYSIS",
    "beneficiaryVASPdid": "did:ethr:0x2c090aef98aa7fa539509da570d559aaa8a46e55",
    "beneficiaryVASPname": "Bitso.com",
    "errors": [
        "beneficiaryNameMissing",
        "beneficiaryAccountNumberMissing"
    ],
    "warnings": [
        "originatorCustomerIdentificationMissing"
    ]
}

Since blockchain analytics was able to identify the VASP name, you can pass it as beneficiaryVASPname in txCreate (stage 2) when you have collected the remaining information so that isValid=true.

When the beneficiary VASP is automatically identified by the address book or blockchain analytics, you do not need to ask your customer to select it and instead only display the remaining:


Known VASP (manually selected)

{
    "isValid": false,
    "type": "TRAVELRULE",
    "beneficiaryAddressType": "UNKNOWN",
    "addressSource": "UNKNOWN",
    "errors": [
        "beneficiaryNameMissing",
        "beneficiaryOwnershipProofMissing"
    ]
}

If your address book or blockchain analytics did not identify the VASP behind the destination address, we suggest you ask your customer to select the correct VASP from our Network.

You can do this by calling tfSimpleVASPs where you pass the name of the VASP provided by your customer in "q" and it will return all VASPs that matches that string. Your customer should then select the correct VASP from the list and continues with the withdrawal request:

Based on what the customer inputs into the search bar, the following API request is made:

https://api.notabene.dev/tf/simple/vasps?q=Hello&fields=did, name, verificationStatus, country, website, isRegulated&per_page=100

It will then return all the VASPs in our directory that matches that input:

{
    "vasps": [
        {
            "did": "did:ethr:0x46a7ed5813ce735387df2bfb245bd7722e0de992",
            "name": "HelloCrypto",
            "verificationStatus": "VERIFIED",
            "country": "AE",
            "website": "https://hellocrypto.invalid",
            "isRegulated": "YES"
        },
        {
            "did": "did:ethr:0x480ee33ef61466730182f33f7495865f4add2e4c",
            "name": "HelloNewVASP",
            "verificationStatus": null,
            "country": null,
            "website": null,
            "isRegulated": null
        }
    ],
    "pagination": {
        "page": 0,
        "per_page": 100,
        "total": 2,
        "order": "name:ASC"
    }
}

Your user then selects the correct VASP from the list and continues with the other travel rule requirements:

This information can then be passed once more to txValidateFull to make sure that everything is OK and you get isValid=true:

{
    "transactionAsset": "ETH",
    "destination": "0xddfAbCdc4D8FfC6d5beaf154f18B778f892A0740",
    "transactionAmount": "10000000000000000000",
    "originatorVASPdid": "{{vaspDID}}",
    "originatorEqualsBeneficiary": false,
    "travelRuleBehavior": true,
    "beneficiaryVASPdid": "did:ethr:0x46a7ed5813ce735387df2bfb245bd7722e0de992",
    "beneficiaryVASPname":"HelloCrypto",
    "beneficiaryName": "John Wayne",
    "beneficiaryAccountNumber": "0xddfAbCdc4D8FfC6d5beaf154f18B778f892A0740"
}

The response that confirms that all the data is present and you should push the collected data to the backend to create the TR transfer:

{
    "isValid": true,
    "type": "TRAVELRULE",
    "beneficiaryAddressType": "UNKNOWN",
    "addressSource": "UNKNOWN"
}

Unknown/unlisted VASP

If the VASP was not automatically identified by your blockchain analytics provider and couldn't be selected from our VASP directory by your customer, it will need to be created. The example below shows how to pass the name of the unknown VASP during the validation step:

{
    "transactionAsset": "ETH",
    "destination": "0x00000000219ab540356cbb839cbe05303d7705fa",
    "transactionAmount": "10000000000000000000",
    "originatorVASPdid": "{{vaspDID}}",
    "originatorEqualsBeneficiary": false,
    "beneficiaryVASPname": "Arkham Cryptonium",
    "beneficiaryName": "Bruce Wayne",
    "beneficiaryAccountNumber": "0x00000000219ab540356cbb839cbe05303d7705fa"
}

Unhosted wallet

🚧

What does the regulation say?

VASPs are required to collect relevant beneficiary information on unhosted wallets from their own customer. Other countries will require VASPs to apply additional mitigation measures or limit transactions with unhosted wallets (such as verifying the identity of the unhosted wallet owner, or performing enhanced due diligence).

If you allow your customer to send virtual assets to an unhosted wallet, there might be specific requirements from your regulator about what you need to do. For example, you might be required to collect proof of ownership or proof of control.

To collect this proof, you have a few options depending on whether your customer is sending the funds to themselves:

📘

1st party unhosted

  • Not allowed;
  • Cryptographic proof;
  • Self-declaration (check box);
  • Proof of control is not needed.

Or to someone else:

📘

3rd party unhosted

  • Not allowed;
  • Collect beneficiary name + Self-declaration (check box).

In txValidate, the unhosted wallet proof is added in beneficiaryProof where "proof" can be the cryptographic signature or true/false/etc and "type" is a string that should identify how they provided that proof (Metamask/WalletConnect/Check-box/etc):

beneficiaryProof examples

{
    "transactionAsset": "ETH",
    "destination": "0xddfAbCdc4D8FfC6d5beaf154f18B778f892A0740",
    "transactionAmount": "10000000000000000000",
    "originatorVASPdid": "{{vaspDID}}",
    "originatorEqualsBeneficiary": true,
    "beneficiaryProof": {
        "proof": "checked",
        "type": "checkbox_confirmation"
    }
}
{
    "transactionAsset": "ETH",
    "destination": "0xddfAbCdc4D8FfC6d5beaf154f18B778f892A0740",
    "transactionAmount": "10000000000000000000",
    "originatorVASPdid": "{{vaspDID}}",
    "originatorEqualsBeneficiary": true,
    "beneficiaryProof": {
        "proof": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c",
        "type": "signTypedData"
    }
}
{
    "transactionAsset": "ETH",
    "destination": "0xddfAbCdc4D8FfC6d5beaf154f18B778f892A0740",
    "transactionAmount": "10000000000000000000",
    "originatorVASPdid": "{{vaspDID}}",
    "originatorEqualsBeneficiary": true,
    "beneficiaryProof": {
        "proof": "N/A",
        "type": "N/A"
    }
}

Example of checkbox confirmation:

If you want to / require proof of control, you need to integrate with Metamask/WalletConnect/etc. directly:


Final validation

After reacting to the response of the initial txValidate request and collecting the necessary information about the beneficiary, you can perform a final request to confirm that you have everything needed:

{
    "transactionAsset": "BTC",
    "transactionAmount": "{{$randomInt}}{{$randomInt}}{{$randomInt}}",
    "originatorVASPdid": "{{vaspDID}}",
    "originatorEqualsBeneficiary": false,
    "travelRuleBehavior": false,
    "validatePartyFields": "BENEFICIARY",
    "beneficiaryVASPdid": "did:ethr:0xd4bd902ec78578f33a20ff601504d2ab324cfab9",
    "transactionBlockchainInfo": {
        "origin": "{{$randomBitcoin}}",
        "destination": "{{$randomBitcoin}}"
    },
    "beneficiary": {
        "beneficiaryPersons": [
            {
                "naturalPerson": {
                    "name": [
                        {
                            "nameIdentifier": [
                                {
                                    "primaryIdentifier": "{{$randomLastName}}",
                                    "secondaryIdentifier": "{{$randomFirstName}}"
                                }
                            ]
                        }
                    ]
                }
            }
        ],
        "accountNumber": [
            "{{$randomBankAccount}}"
        ]
    }
}

If you have all the information needed, the response will say isValid=true:

{
    "isValid": true,
    "type": "TRAVELRULE",
    "beneficiaryAddressType": "UNKNOWN",
    "addressSource": "UNKNOWN",
    "beneficiaryVASPname": "Notabene VASP SG",
    "beneficiaryVASPdid": "did:ethr:0xd4bd902ec78578f33a20ff601504d2ab324cfab9",
    "errors": []
}

Once isValid=true, you should move the information you just used in this final validation request to your back-end, add information about your customer (the originator) and create the actual travel rule message as described in this guide.


Building your own widget