Data collection

When your customer requests a withdrawal, usually by entering a destination address, an asset and an amount, you collect its Travel Rule data before you create the transfer. You can use the Embedded component, which works out the requirements and shows the right fields, or build your own UI on the requirements the API returns. Either way, you only collect what the transfer actually needs.

Each approach takes one call before you create the transfer: a delegate token for the component, or an assessment for your own UI.

Before you start

  • An access token, as described in Authentication. The examples use the sandbox base URL.
  • Your customer registered with Notabene, as described in Customers, so the transfer can refer to them by their customer id.

Whose rules apply

Your own jurisdiction’s rules apply, as set in the dashboard under Authorization Rules › Travel Rule. That is also where you can choose to meet the counterparty’s rules when they ask for more.

Portugal, for example, applies from the first euro and asks for both names and both account numbers. It also asks for one of the originator’s date and place of birth, customer identification, address or national identification.

A. With the Embedded component

The component works out the requirements and shows your customer only the fields this transfer needs.

A1. Get a delegate token

Your backend uses its own access token to request a delegate token for this customer, so your access token stays on your server and never reaches the browser.

API reference
curl https://sandbox.notabene.id/api/v1/entities/did:web:originator.example:pt/delegate-token \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "customer_ref": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
  }'const response = await fetch('https://sandbox.notabene.id/api/v1/entities/did:web:originator.example:pt/delegate-token', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "customer_ref": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
  }),
});
const answer = response.status === 204 ? null : await response.json();import requests

response = requests.post(
    'https://sandbox.notabene.id/api/v1/entities/did:web:originator.example:pt/delegate-token',
    headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
    json={
      "customer_ref": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
    },
)
answer = None if response.status_code == 204 else response.json()
What to replace
  • did:web:originator.example:pt: Your institution’s DID
  • urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27: The id of your customer
Response201
{
  "access_token": "eyJhbGciOiJFZERTQSIs",
  "token_type": "Bearer",
  "expires_in": 3600,
  "jti": "c03044be-8e0b-4bbe-9d2c-c7b28ac3aa5f"
}
curl https://sandbox.notabene.id/api/v1/entities/did:web:originator.example:pt/delegate-token \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "customer_ref": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
  }'
const response = await fetch('https://sandbox.notabene.id/api/v1/entities/did:web:originator.example:pt/delegate-token', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "customer_ref": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
  }),
});
const answer = response.status === 204 ? null : await response.json();
import requests

response = requests.post(
    'https://sandbox.notabene.id/api/v1/entities/did:web:originator.example:pt/delegate-token',
    headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
    json={
      "customer_ref": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
    },
)
answer = None if response.status_code == 204 else response.json()
{
  "access_token": "eyJhbGciOiJFZERTQSIs",
  "token_type": "Bearer",
  "expires_in": 3600,
  "jti": "c03044be-8e0b-4bbe-9d2c-c7b28ac3aa5f"
}

Pass the access_token to your frontend, which opens the component with it in the next step. A delegate token only lets the component act on behalf of this one customer, while the access token your backend uses is covered in Authentication.

A2. Open the Embedded component

Your frontend opens the component with the delegate token and the withdrawal details. When your customer is done, the component returns the collected data to your app (the event and the data format (placeholder)). Its options and events are described in Embedded component (placeholder).

B. With your own UI

The API returns the requirements, and your app shows the fields.

B1. Assess the transfer

Before asking your customer for anything, ask Notabene what this transfer requires. The response tells you whether the Travel Rule applies, which fields you need to collect and which institution holds the destination address. This call does not create anything or contact the counterparty.

API reference
curl https://sandbox.notabene.id/api/v1/transfers/assess \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "asset": "ETH",
    "amount": "0.5",
    "destinationAddress": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
  }'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/assess', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "asset": "ETH",
    "amount": "0.5",
    "destinationAddress": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
  }),
});
const answer = response.status === 204 ? null : await response.json();import requests

response = requests.post(
    'https://sandbox.notabene.id/api/v1/transfers/assess',
    headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
    json={
      "asset": "ETH",
      "amount": "0.5",
      "destinationAddress": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
    },
)
answer = None if response.status_code == 204 else response.json()
What to replace
  • eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed: The destination address your customer entered
Response200
{
  "travelRule": "Applies",
  "isTravelRule": true,
  "presentationDefinitionUrl": "https://pd.notabene.id/ivms101/v2/PT-0.json",
  "threshold": { "amount": "0", "currency": "EUR" },
  "required": [
    "originatorName",
    "originatorAccountNumber",
    "beneficiaryName",
    "beneficiaryAccountNumber",
    {
      "oneOf": [
        "originatorDateAndPlaceOfBirth",
        "originatorCustomerIdentification",
        "originatorGeographicAddress",
        "originatorNationalIdentification"
      ]
    }
  ],
  "beneficiaryVasp": {
    "did": "did:web:beneficiary.example:pt",
    "name": "Example Exchange",
    "jurisdiction": "PT"
  }
}
curl https://sandbox.notabene.id/api/v1/transfers/assess \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "asset": "ETH",
    "amount": "0.5",
    "destinationAddress": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
  }'
const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/assess', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "asset": "ETH",
    "amount": "0.5",
    "destinationAddress": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
  }),
});
const answer = response.status === 204 ? null : await response.json();
import requests

response = requests.post(
    'https://sandbox.notabene.id/api/v1/transfers/assess',
    headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
    json={
      "asset": "ETH",
      "amount": "0.5",
      "destinationAddress": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
    },
)
answer = None if response.status_code == 204 else response.json()
{
  "travelRule": "Applies",
  "isTravelRule": true,
  "presentationDefinitionUrl": "https://pd.notabene.id/ivms101/v2/PT-0.json",
  "threshold": { "amount": "0", "currency": "EUR" },
  "required": [
    "originatorName",
    "originatorAccountNumber",
    "beneficiaryName",
    "beneficiaryAccountNumber",
    {
      "oneOf": [
        "originatorDateAndPlaceOfBirth",
        "originatorCustomerIdentification",
        "originatorGeographicAddress",
        "originatorNationalIdentification"
      ]
    }
  ],
  "beneficiaryVasp": {
    "did": "did:web:beneficiary.example:pt",
    "name": "Example Exchange",
    "jurisdiction": "PT"
  }
}

Amounts are written in the asset’s whole units, for example 0.5 ETH rather than an amount in wei. For an asset Notabene cannot price, add fiatAmountUsd with your own valuation.

Reading the assessment

The travelRule value in the response from POST /api/v1/transfers/assess tells you what to collect.

travelRuleWhat to collect
AppliesThe fields listed in required. A oneOf group is met by any one of its fields.
ExemptNothing. The Travel Rule does not apply to this transfer, for example because the amount is below the threshold.
UndeterminedNotabene cannot tell whether the Travel Rule applies. Common practice is to send both names and both account numbers, so ask your customer for the beneficiary’s name.
travelRuleWhat to collect
AppliesThe fields listed in required. A oneOf group is met by any one of its fields.
ExemptNothing. The Travel Rule does not apply to this transfer, for example because the amount is below the threshold.
UndeterminedNotabene cannot tell whether the Travel Rule applies. Common practice is to send both names and both account numbers, so ask your customer for the beneficiary’s name.

If no institution is found

If the response does not name an institution for the destination address, ask your customer where the funds are going.

  • If it is an account at an exchange, let your customer pick the institution from the Notabene network, and pass it as beneficiary_entity_did when you create the transfer.
  • If it is their own wallet, there is no name to ask for, so collect a proof that the wallet belongs to them (POST /api/v1/customers/{customer}/wallets/{address}/proofs), as described in Wallets.
  • If it is someone else’s wallet, ask your customer for the owner’s name.

You can search the network for the institution as your customer types.

curl --get https://sandbox.notabene.id/api/v1/network/entities \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --data-urlencode 'q=Example Exchange'
const url = new URL('https://sandbox.notabene.id/api/v1/network/entities');
url.searchParams.set('q', 'Example Exchange');

const response = await fetch(url, {
  method: 'GET',
  headers: {
    Authorization: 'Bearer YOUR_ACCESS_TOKEN',
  },
});
const answer = response.status === 204 ? null : await response.json();
import requests

response = requests.get(
    'https://sandbox.notabene.id/api/v1/network/entities',
    headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
    params={
      "q": "Example Exchange"
    },
)
answer = None if response.status_code == 204 else response.json()
{
  "data": [
    {
      "did": "did:web:beneficiary.example:pt",
      "name": "Example Exchange",
      "legalName": "Example Exchange, S.A.",
      "jurisdiction": "PT",
      "status": "receive_respond_send",
      "verificationStatus": "verified"
    }
  ]
}

Results come in pages of up to 100, starting at page 1.

B2. Collect the required fields

Show your customer only the beneficiary fields listed in the assessment, which is usually just the beneficiary’s name. Information about your own customer comes from your records, so there is no need to ask for it again. A oneOf group is satisfied by any one of its fields, so use one you already hold.

Where each field comes from

Each value in required stands for an IVMS-101 field. The presentationDefinitionUrl in the assessment is the full rule set of the jurisdiction that applies, and the list of jurisdictions links every jurisdiction’s rule set with its threshold.

Value in requiredThe IVMS-101 fieldWhere it comes from
originatorNameThe originator’s naturalPerson.name, or legalPerson.name for an organization.Your records.
originatorAccountNumberThe originator’s accountNumber.Your records.
originatorGeographicAddressThe originator’s geographicAddress.Your records.
originatorNationalIdentificationThe originator’s nationalIdentification.Your records.
originatorDateAndPlaceOfBirthThe originator’s dateAndPlaceOfBirth, for a person only.Your records.
originatorCustomerIdentificationThe originator’s customerIdentification.Your records.
beneficiaryNameThe beneficiary’s naturalPerson.name, or legalPerson.name for an organization.Ask your customer for the name of the account holder at the destination.
beneficiaryAccountNumberThe beneficiary’s accountNumber.The destination address your customer entered.
beneficiaryGeographicAddressThe beneficiary’s geographicAddress.Ask your customer, when it is listed.
Value in requiredThe IVMS-101 fieldWhere it comes from
originatorNameThe originator’s naturalPerson.name, or legalPerson.name for an organization.Your records.
originatorAccountNumberThe originator’s accountNumber.Your records.
originatorGeographicAddressThe originator’s geographicAddress.Your records.
originatorNationalIdentificationThe originator’s nationalIdentification.Your records.
originatorDateAndPlaceOfBirthThe originator’s dateAndPlaceOfBirth, for a person only.Your records.
originatorCustomerIdentificationThe originator’s customerIdentification.Your records.
beneficiaryNameThe beneficiary’s naturalPerson.name, or legalPerson.name for an organization.Ask your customer for the name of the account holder at the destination.
beneficiaryAccountNumberThe beneficiary’s accountNumber.The destination address your customer entered.
beneficiaryGeographicAddressThe beneficiary’s geographicAddress.Ask your customer, when it is listed.

When the destination is your customer’s own wallet, they are also the beneficiary, so take the beneficiary fields from your records and collect a proof of ownership instead.

Then create the transfer

Both approaches end the same way, with your backend creating the transfer from the data you collected. Notabene checks it against the requirements again, applies your policies and sends the transfer to the counterparty institution.

Withdrawals covers this step, and its call and response are in Withdrawals, step 1.

Notabene finds the institution from settlement_address, and if your customer picked the institution themselves, you also pass beneficiary_entity_did. When the destination is your customer’s own wallet, set beneficiary_id to the wallet’s did:pkh and include the proof in beneficiary_proof_of_ownership.

When a call fails

AnswerWhat it means
400 with expected required property amount to be presentThe assessment needs the asset and the amount.
400 with expected required property customer_ref to be presentA delegate token is for one customer. Send their id as customer_ref.
401 with invalid bearer tokenThe delegate token has expired. It lasts 1 hour, so request a new one.
403 with token subject does not match customerThe delegate token belongs to another customer.
AnswerWhat it means
400 with expected required property amount to be presentThe assessment needs the asset and the amount.
400 with expected required property customer_ref to be presentA delegate token is for one customer. Send their id as customer_ref.
401 with invalid bearer tokenThe delegate token has expired. It lasts 1 hour, so request a new one.
403 with token subject does not match customerThe delegate token belongs to another customer.

Errors lists every other answer.

Common mistakes

MistakeWhat happensInstead
Giving your app your access tokenYour app can then do everything your backend can.Give it a delegate token, which only works for one customer.
Asking your customer for their own detailsYour customer types again what you already hold.Take the originator’s fields from your records, and ask only for the beneficiary’s.
The amount in the asset’s smallest unit, such as weiThe assessment reads it in whole units, so it may apply the wrong threshold.Send "0.5" for 0.5 ETH.
MistakeWhat happensInstead
Giving your app your access tokenYour app can then do everything your backend can.Give it a delegate token, which only works for one customer.
Asking your customer for their own detailsYour customer types again what you already hold.Take the originator’s fields from your records, and ask only for the beneficiary’s.
The amount in the asset’s smallest unit, such as weiThe assessment reads it in whole units, so it may apply the wrong threshold.Send "0.5" for 0.5 ETH.

How it works

Embedded component
Your own UI
Steps
Embedded component
Your own UI

Done by Notabene

Only when needed

← → the step before and after · Esc all steps
Step
Your customer requests a withdrawal
Your customer, in your app

With the destination address, the asset and the amount.

← → the step before and after · Esc all steps
Embedded component · Step A1
A1Get a delegate token
Your backend

Your backend requests one for this customer: A1. Get a delegate token.

← → the step before and after · Esc all steps
Embedded component
Open the Embedded component
Your frontend, then your customer

It asks your customer for what the rules require: A2. Open the Embedded component.

← → the step before and after · Esc all steps
Your own UI · Step B1
B1Assess the transfer
Your backend

Ask Notabene what this transfer requires: B1. Assess the transfer.

← → the step before and after · Esc all steps
Your own UI · Only when needed
If no institution is found
Your app

Ask your customer where the funds are going: If no institution is found.

← → the step before and after · Esc all steps
Your own UI · Step B2
B2Collect the required fields
Your app

Ask only for what the assessment lists: B2. Collect the required fields.

← → the step before and after · Esc all steps
Step
Create the transfer
Your backend

With the data you collected: Then create the transfer.

← → the step before and after · Esc all steps

Did this page help you?