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": "12000000000000000000",
    "originatorVASPdid": "{{vaspDID}}",
    "originatorEqualsBeneficiary": false,
    "validatePartyFields": "BENEFICIARY",
    "beneficiaryRef": "[email protected]:1DHVemqiRrfS7J5tQDauNc9rG",
    "originatorRef": "[email protected]",
    "transactionBlockchainInfo": {
        "origin": "3jtM2SZsuWywbNJXsXCWxAPUQ",
        "destination": "1DHVemqiRrfS7J5tQDauNc9rG"
    }
}

The goal of this initial validation will tell you three key points:

  • If the value of this transaction is above or below the travel rule threshold,
  • If the destination address was identified,
  • What information does your customer need to provide about the beneficiary.

Possible response 1: Known VASP (Internal address book)

In this request, we are using a destination address that you have sent to before. The beneficiary VASP has responded to your travel rule message in the past and confirmed that the address is theirs. Therefore, it has been added to your internal address book:

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

Possible response 2: Known VASP (Hashed address book)

In this request, we are passing a destination address that someone else in the Notabene network is sharing with those who have opted into the Network Discoverability feature:

{
    "isValid": false,
    "type": "TRAVELRULE",
    "beneficiaryAddressType": "HOSTED",
    "addressSource": "ADDRESS_HASH",
    "beneficiaryVASPdid": "did:ethr:0xd4bd902ec78578f33a20ff601504d2ab324cfab9",
    "beneficiaryVASPname": "Notabene VASP SG",
    "errors": [
    ],
    "warnings": [
    ]
}

Possible response 3: Known VASP (Blockchain analytics)

In this call, we are using an address where there was no hit in your internal address book and no hit in the hashed address book, but there was a hit from blockchain analytics (you need to activate your provider in the marketplace):

{
    "isValid": false,
    "type": "TRAVELRULE",
    "beneficiaryAddressType": "HOSTED",
    "addressSource": "CHAINALYSIS", <-- or which ever other provider you use
    "beneficiaryVASPdid": "did:ethr:0x2c090aef98aa7fa539509da570d559aaa8a46e55",
    "beneficiaryVASPname": "Bitso.com",
    "errors": [
        "beneficiaryNameMissing",
        "beneficiaryAccountNumberMissing"
    ],
    "warnings": [
        "originatorCustomerIdentificationMissing"
    ]
}

Possible response 4: Unknown VASP (Manually search and select)

If the address books or blockchain analytics did not identify the VASP behind the destination address, it will default to NON_CUSTODIAL as the type. However, this must be confirmed by asking your customer if the wallet is hosted or not.

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

If they say it is hosted, they should search and select the correct VASP from our Network by calling tfSimpleVASPs where you pass the name of the VASP provided by your customer in the "q"-parameter and it will return all VASPs that match that string. Your customer should then select the correct VASP from the list and continue with the withdrawal request:

For example, your customer knows the address is hosted by a VASP called "HelloCrypto", so they type that into a search field on your side, and you call:

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 match the 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:


Possible response 5: Unknown VASP (Doesn't exist in Notabene)

If your customer couldn't find the VASP they were looking for when searching with tfSimpleVASPs, it might be one where we don't have their profile registered yet.

For example, your customer knows the address is hosted by "Arkham Cryptonium" so they search for it:

{
    "vasps": [],
    "pagination": {
        "page": 0,
        "per_page": 999,
        "total": 0,
        "order": "name:ASC"
    }
}

As you can see, there are no possible hits. If this happens and to not block the transfer because of a missing profile, Notabene allows you to pass the missing VASP name as beneficiaryVASPname in your txValidateFull/txCreate call:

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

Possible response 6: Unknown VASP (Unhosted wallet)

Same as inPossible response 4, where there is no hit in the address books or blockchain analytics, you ask your customer to declare if the address is hosted or unhosted.

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

If they say it is an unhosted address, you need to potentially collect some proof, depending on whether it is their own unhosted wallet or someone else's. For example, your regulator might require you to collect proof of ownership or proof of control if it is their own.

🚧

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

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

📘

1st party unhosted

  • Cryptographic proof; or
  • Self-declaration (check box); or
  • Proof of control is not needed in your jurisdiction

Or to someone else:

📘

3rd party unhosted

  • This could be not allowed because you cannot collect proof of control; or
  • Collect beneficiary name + have a self-declaration (check-box) proof.

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):

Unhosted wallet proofs

{
    "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, or use our widget which has this pre-buildt:


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": "ETH",
    "transactionAmount": "12000000000000000000",
    "originatorVASPdid": "{{vaspDID}}",
    "originatorEqualsBeneficiary": false,
    "validatePartyFields": "BENEFICIARY",
    "beneficiaryVASPdid": "did:ethr:0xd4bd902ec78578f33a20ff601504d2ab324cfab9",
    "beneficiaryRef": "[email protected]",
    "originatorRef": "[email protected]",
    "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",
    "beneficiaryVASPdid": "did:ethr:0xd4bd902ec78578f33a20ff601504d2ab324cfab9",
    "beneficiaryVASPname": "Notabene VASP SG",
    "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.


Checking both thresholds

Because there are differences in the threshold for when travel rule data needs to be sent and received in different jurisdictions, there might be cases where you are not required to send, but the recipient is required to recieve.

If you choose not to send a travel rule message because you're not required to (below your threshold), you might risk getting those funds frozen on the other side or returned to you.

If you want to see if your counterparty VASP requires a travel rule message to be sent, you can enable "travelRuleBehavior": true in your txValidateFull payload, we will check the threshold for both jurisdictions.

🚧

GDPR

Even if you want to send travel rule messages when your recipient requries it, it might not be allowed from your local GDPR regulation. Please check that before enabling this option.

Example

  • United Kingdom threshold €1,000 (~$1,080)
  • United States threshold $3,000
  • If a US VASP is sending a transfer valued above USD3,000, you will always be able to send the Travel Rule transfer
  • If a US VASP is sending a transfer valued at USD1,500 to a UK VASP, enables you to send the Travel Rule transfer even though it’s below the US threshold of USD3,000.
  • If a US VASP is sending a transfer valued at USD500 to a UK VASP, you won’t be able to send the Travel Rule transfer since it’s below both the UK and US threshold. It will be stored in your account for audit purposes.