Moving from V1 to v2.1

V1 and v2.1 are separate platforms, so moving means a new account and a new integration rather than an upgrade. Your v2.1 account has its own DID, API credentials and webhook endpoint, and your backend replaces each V1 call with its v2.1 counterpart.

Only the access token request keeps its shape. Most flows also take fewer calls, because your policies authorize every transfer that passes them, where V1 often waited for your backend to approve, confirm or accept it.

Main differences

TopicV1v2.1
Base URLhttps://api.notabene.id, and https://api.notabene.dev for testing.https://sandbox.notabene.id/api/v1 in sandbox, and your region’s address in production, as Authentication lists.
Token audiencehttps://api.notabene.idThe environment’s address, such as https://sandbox.notabene.id.
Your DIDYour V1 DID, such as a did:ethr.A did:web created with your account, as in Create your account.
Your institution on a callvaspDID, originatorVASPdid or beneficiaryVASPdid.Taken from your access token, so you leave it out.
AmountstransactionAmount in the asset’s smallest unit, such as wei.amount in whole units, such as "0.5" for 0.5 ETH.
AddressesA bare address, with the asset naming the chain.CAIP-10, such as eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed. Transfers also take a bare address.
Your customersSent in full on every transfer, in originatorPersons or beneficiaryPersons.Registered once, then named on each transfer by your own id.
IVMS-101 namesname as an array, with nameIdentifierType.name as an object, with naturalPersonNameIdentifierType.
DecisionstxApprove, txAccept and txDecline, unless rules made them.Your policies approve, flag or reject. You call only for a flagged transfer.
Personal data you encryptencrypted and pii on the transfer.ivms_encrypted, one JWE per person, as Encryption describes.
WebhooksRegistered through the API.Added in the dashboard, under Developers › Webhooks, with signed deliveries.
TopicV1v2.1
Base URLhttps://api.notabene.id, and https://api.notabene.dev for testing.https://sandbox.notabene.id/api/v1 in sandbox, and your region’s address in production, as Authentication lists.
Token audiencehttps://api.notabene.idThe environment’s address, such as https://sandbox.notabene.id.
Your DIDYour V1 DID, such as a did:ethr.A did:web created with your account, as in Create your account.
Your institution on a callvaspDID, originatorVASPdid or beneficiaryVASPdid.Taken from your access token, so you leave it out.
AmountstransactionAmount in the asset’s smallest unit, such as wei.amount in whole units, such as "0.5" for 0.5 ETH.
AddressesA bare address, with the asset naming the chain.CAIP-10, such as eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed. Transfers also take a bare address.
Your customersSent in full on every transfer, in originatorPersons or beneficiaryPersons.Registered once, then named on each transfer by your own id.
IVMS-101 namesname as an array, with nameIdentifierType.name as an object, with naturalPersonNameIdentifierType.
DecisionstxApprove, txAccept and txDecline, unless rules made them.Your policies approve, flag or reject. You call only for a flagged transfer.
Personal data you encryptencrypted and pii on the transfer.ivms_encrypted, one JWE per person, as Encryption describes.
WebhooksRegistered through the API.Added in the dashboard, under Developers › Webhooks, with signed deliveries.

Setup

Set up a sandbox account first, and repeat the setup in production once your integration works there. Sandbox and production are separate accounts, each with its own credentials, webhook endpoint, customers and addresses.

01

Create your account and have it activated

In the dashboard.

Page

Create your account

02

Create an API credential

In the dashboard, under Developers › API Credentials.

Page

Authentication

03

Publish your DID document

In production. In sandbox, Notabene publishes it.

Page

Host your DID document

04

Set your policies

In the dashboard, under Authorization Rules › Policies. A new account flags every transfer until you change its general default.

Page

Policies

05

Choose your requirements preset

In the dashboard, under Authorization Rules › Requirements.

Page

Requirements

06

Add your webhook endpoint

In the dashboard, under Developers › Webhooks.

Page

Webhooks

07

Register your customers and their deposit addresses

Through the API, as below.

Page

Customers

StepWherePage
Create your account and have it activatedIn the dashboard.Create your account
Create an API credentialIn the dashboard, under Developers › API Credentials.Authentication
Publish your DID documentIn production. In sandbox, Notabene publishes it.Host your DID document
Set your policiesIn the dashboard, under Authorization Rules › Policies. A new account flags every transfer until you change its general default.Policies
Choose your requirements presetIn the dashboard, under Authorization Rules › Requirements.Requirements
Add your webhook endpointIn the dashboard, under Developers › Webhooks.Webhooks
Register your customers and their deposit addressesThrough the API, as below.Customers

Counterparty institutions still on V1 or V2 go on exchanging Travel Rule data with you once you are on v2.1.

Switching over

Outgoing transfers move in one step. From the day your v2.1 account is live in production, every withdrawal goes from it, and your V1 account sends no new ones.

Incoming transfers can still reach your V1 account for a while, because some counterparties keep sending to it. Notabene hides your V1 account from the network and removes its addresses, so counterparties no longer find it, while you can still sign in and handle what arrives there.

Once counterparties have stopped sending to your V1 account, Notabene moves your V1 data into a read-only copy and deletes the V1 account. Where the read-only copy of your V1 data is kept is still to be confirmed. (placeholder)

Calls

Each V1 call has a v2.1 counterpart, or none where your policies now do the work. These are the calls most integrations make, in the order a transfer meets them.

V1v2.1What changes
The access token requestThe same requestYour v2.1 client_id and client_secret, and the v2.1 audience.
customerTokenPOST/api/v1/entities/{entityDID}/delegate-tokenIt covers one customer you registered, and lasts 1 hour.
txValidateFullPOST/api/v1/transfers/assessIt takes the asset, amount and destination, and answers what to collect. It is optional.
address-ownershipsPOST/api/v1/wallet-ownerships/discoverThe address goes in the body, and the answer names the institution that holds it.
tfSimpleVASPsGET/api/v1/network/entitiesSearch by name with q.
txCreatePOST/api/v1/transfersIt names your customer by id, with the amount in whole units.
txUpdate with txHashPOST/api/v1/transfers/{id}/settleThe hash goes in settlement_id.
txConfirmPOST/api/v1/wallets, or confirmed on the address requirementA registered address needs no answer.
txRejectrejected on the address requirementIt rejects the transfer.
registerCustomerPOST/api/v1/customers, then POST/api/v1/walletsThe customer is registered first, and each address then names the customer’s id.
txNotifyGET/api/v1/transfers/match, then POST/api/v1/transfersYou look the deposit up first, and a transfer you create asks the sender for its data.
txUpdate to complete a depositPOST/api/v1/transfers, then PATCH/api/v1/transfers/{id}/parties/originatorThe sending institution goes on the create, and a sender your customer declares goes on the transfer afterwards.
V1v2.1What changes
The access token requestThe same requestYour v2.1 client_id and client_secret, and the v2.1 audience.
customerTokenPOST /api/v1/entities/{entityDID}/delegate-tokenIt covers one customer you registered, and lasts 1 hour.
txValidateFullPOST /api/v1/transfers/assessIt takes the asset, amount and destination, and answers what to collect. It is optional.
address-ownershipsPOST /api/v1/wallet-ownerships/discoverThe address goes in the body, and the answer names the institution that holds it.
tfSimpleVASPsGET /api/v1/network/entitiesSearch by name with q.
txCreatePOST /api/v1/transfersIt names your customer by id, with the amount in whole units.
txUpdate with txHashPOST /api/v1/transfers/{id}/settleThe hash goes in settlement_id.
txConfirmPOST /api/v1/wallets, or confirmed on the address requirementA registered address needs no answer.
txRejectrejected on the address requirementIt rejects the transfer.
registerCustomerPOST /api/v1/customers, then POST /api/v1/walletsThe customer is registered first, and each address then names the customer’s id.
txNotifyGET /api/v1/transfers/match, then POST /api/v1/transfersYou look the deposit up first, and a transfer you create asks the sender for its data.
txUpdate to complete a depositPOST /api/v1/transfers, then PATCH /api/v1/transfers/{id}/parties/originatorThe sending institution goes on the create, and a sender your customer declares goes on the transfer afterwards.

Some integrations also make the calls below, for decisions their backend took itself or to read transfers back.

V1v2.1What changes
txApprove, txAccept, txDeclineNo call, unless your policies flag the transferA flagged transfer takes force-authorize or reject.
txCancelPOST/api/v1/transfers/{id}/cancelOnly before settlement, and only by the institution that created the transfer.
txRedirectPOST/api/v1/transfers/{id}/agents/replaceYour institution on the transfer is replaced by the one that should receive it.
txInfo, txListGET/api/v1/transfers/{id}, GET/api/v1/transfersYour institution comes from the token.
/webhookThe dashboard, under Developers › WebhooksFour events, signed.
V1v2.1What changes
txApprove, txAccept, txDeclineNo call, unless your policies flag the transferA flagged transfer takes force-authorize or reject.
txCancelPOST /api/v1/transfers/{id}/cancelOnly before settlement, and only by the institution that created the transfer.
txRedirectPOST /api/v1/transfers/{id}/agents/replaceYour institution on the transfer is replaced by the one that should receive it.
txInfo, txListGET /api/v1/transfers/{id}, GET /api/v1/transfersYour institution comes from the token.
/webhookThe dashboard, under Developers › WebhooksFour events, signed.

Access token

The request is the same, with the credential from your v2.1 account and the audience of its environment. Every call then sends the token as Authorization: Bearer, as in V1.

V1Access token

HTTP
POST https://auth.notabene.id/oauth/token

{
  "client_id": "YOUR_V1_CLIENT_ID",
  "client_secret": "YOUR_V1_CLIENT_SECRET",
  "grant_type": "client_credentials",
  "audience": "https://api.notabene.dev"
}

v2.1Access token

HTTP
POST https://auth.notabene.id/oauth/token

{
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET",
  "grant_type": "client_credentials",
  "audience": "https://sandbox.notabene.id"
}
POST https://auth.notabene.id/oauth/token

{
  "client_id": "YOUR_V1_CLIENT_ID",
  "client_secret": "YOUR_V1_CLIENT_SECRET",
  "grant_type": "client_credentials",
  "audience": "https://api.notabene.dev"
}
POST https://auth.notabene.id/oauth/token

{
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET",
  "grant_type": "client_credentials",
  "audience": "https://sandbox.notabene.id"
}

In production, the audience is your region’s address, which Authentication lists.

Tokens for your customer’s app

V1 gave your frontend a customer token through customerToken. In v2.1 your backend requests a delegate token for one customer you registered, with your own DID in the path and the customer’s id as customer_ref.

V1customerToken

HTTP
POST https://api.notabene.dev/auth/customerToken

{
  "vaspDID": "did:ethr:0x6c3b1f6a3e2d4c5b8a9e0f1d2c3b4a5968778695",
  "customerRef": "customer-123"
}

v2.1Get a delegate token

HTTPJSON
POST /api/v1/entities/did:web:yourexchange.example:pt/delegate-token

{
  "customer_ref": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
}
{
  "access_token": "eyJhbGciOiJFZERTQSIs",
  "token_type": "Bearer",
  "expires_in": 3600,
  "jti": "c03044be-8e0b-4bbe-9d2c-c7b28ac3aa5f"
}
POST https://api.notabene.dev/auth/customerToken

{
  "vaspDID": "did:ethr:0x6c3b1f6a3e2d4c5b8a9e0f1d2c3b4a5968778695",
  "customerRef": "customer-123"
}
POST /api/v1/entities/did:web:yourexchange.example:pt/delegate-token

{
  "customer_ref": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
}
{
  "access_token": "eyJhbGciOiJFZERTQSIs",
  "token_type": "Bearer",
  "expires_in": 3600,
  "jti": "c03044be-8e0b-4bbe-9d2c-c7b28ac3aa5f"
}

The customer must be registered first, so register customers before your app asks for a token. A delegate token works only on the routes under /api/v1/customers/{customer}/, which Authentication lists, and the embedded components in Data collection use it the same way.

Customers and addresses

In V1, your address book tied an address to a customerRef of your own, and each transfer carried your customer’s data again. In v2.1 you register the customer once with that data, then register each address against the customer’s id.

V1registerCustomer

HTTP
POST https://api.notabene.dev/address/registerCustomer

{
  "vaspDID": "did:ethr:0x6c3b1f6a3e2d4c5b8a9e0f1d2c3b4a5968778695",
  "asset": "ETH",
  "address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "customerRef": "customer-123"
}

v2.1Register the customer, then the address

HTTPHTTP
POST /api/v1/customers

{
  "id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
  "type": "Individual",
  "jurisdiction": "PT",
  "profile": {
    "ivms": {
      "naturalPerson": {
        "name": {
          "nameIdentifier": [
            {
              "primaryIdentifier": "Silva",
              "secondaryIdentifier": "Ana",
              "naturalPersonNameIdentifierType": "LEGL"
            }
          ]
        }
      },
      "accountNumber": [
        "acc-ana-001"
      ]
    }
  }
}
POST /api/v1/wallets

{
  "address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "customer_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
}
POST https://api.notabene.dev/address/registerCustomer

{
  "vaspDID": "did:ethr:0x6c3b1f6a3e2d4c5b8a9e0f1d2c3b4a5968778695",
  "asset": "ETH",
  "address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "customerRef": "customer-123"
}
POST /api/v1/customers

{
  "id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
  "type": "Individual",
  "jurisdiction": "PT",
  "profile": {
    "ivms": {
      "naturalPerson": {
        "name": {
          "nameIdentifier": [
            {
              "primaryIdentifier": "Silva",
              "secondaryIdentifier": "Ana",
              "naturalPersonNameIdentifierType": "LEGL"
            }
          ]
        }
      },
      "accountNumber": [
        "acc-ana-001"
      ]
    }
  }
}
POST /api/v1/wallets

{
  "address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "customer_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
}

A bare address on POST /api/v1/wallets answers 400, so send it as CAIP-10, which names the chain where V1 used asset. Leave out customer_id for an omnibus wallet, such as a hot wallet. Customers and Wallets list every field.

Checks before a withdrawal

V1 validated a complete transfer, personal data included. In v2.1 the assessment takes only the asset, the amount and the destination. It answers whether the Travel Rule applies, which data to collect and which institution holds the address.

V1txValidateFull

HTTP
POST https://api.notabene.dev/tx/validate/full

{
  "transactionAsset": "ETH",
  "transactionAmount": "500000000000000000",
  "originatorVASPdid": "did:ethr:0x6c3b1f6a3e2d4c5b8a9e0f1d2c3b4a5968778695",
  "transactionBlockchainInfo": {
    "destination": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
  },
  "originator": {
    "originatorPersons": [
      {
        "naturalPerson": {
          "name": [
            {
              "nameIdentifier": [
                {
                  "primaryIdentifier": "Silva",
                  "secondaryIdentifier": "Ana",
                  "nameIdentifierType": "LEGL"
                }
              ]
            }
          ]
        }
      }
    ],
    "accountNumber": [
      "acc-ana-001"
    ]
  },
  "beneficiary": {
    "beneficiaryPersons": [
      {
        "naturalPerson": {
          "name": [
            {
              "nameIdentifier": [
                {
                  "primaryIdentifier": "Smith",
                  "secondaryIdentifier": "Alice",
                  "nameIdentifierType": "LEGL"
                }
              ]
            }
          ]
        }
      }
    ],
    "accountNumber": [
      "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
    ]
  }
}

v2.1Assess the transfer

HTTPJSON
POST /api/v1/transfers/assess

{
  "asset": "ETH",
  "amount": "0.5",
  "destinationAddress": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
}
{
  "travelRule": "Applies",
  "isTravelRule": true,
  "required": [
    "originatorName",
    "originatorAccountNumber",
    "beneficiaryName",
    "beneficiaryAccountNumber"
  ],
  "beneficiaryVasp": {
    "did": "did:web:beneficiary.example:pt",
    "name": "Example Exchange",
    "jurisdiction": "PT"
  }
}
POST https://api.notabene.dev/tx/validate/full

{
  "transactionAsset": "ETH",
  "transactionAmount": "500000000000000000",
  "originatorVASPdid": "did:ethr:0x6c3b1f6a3e2d4c5b8a9e0f1d2c3b4a5968778695",
  "transactionBlockchainInfo": {
    "destination": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
  },
  "originator": {
    "originatorPersons": [
      {
        "naturalPerson": {
          "name": [
            {
              "nameIdentifier": [
                {
                  "primaryIdentifier": "Silva",
                  "secondaryIdentifier": "Ana",
                  "nameIdentifierType": "LEGL"
                }
              ]
            }
          ]
        }
      }
    ],
    "accountNumber": [
      "acc-ana-001"
    ]
  },
  "beneficiary": {
    "beneficiaryPersons": [
      {
        "naturalPerson": {
          "name": [
            {
              "nameIdentifier": [
                {
                  "primaryIdentifier": "Smith",
                  "secondaryIdentifier": "Alice",
                  "nameIdentifierType": "LEGL"
                }
              ]
            }
          ]
        }
      }
    ],
    "accountNumber": [
      "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
    ]
  }
}
POST /api/v1/transfers/assess

{
  "asset": "ETH",
  "amount": "0.5",
  "destinationAddress": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
}
{
  "travelRule": "Applies",
  "isTravelRule": true,
  "required": [
    "originatorName",
    "originatorAccountNumber",
    "beneficiaryName",
    "beneficiaryAccountNumber"
  ],
  "beneficiaryVasp": {
    "did": "did:web:beneficiary.example:pt",
    "name": "Example Exchange",
    "jurisdiction": "PT"
  }
}

The assessment is optional, because creating the transfer runs the same checks. Data collection shows how to read the answer, or how an embedded component collects the data for you.

Finding the counterparty institution

V1 looked up who held an address with address-ownerships, and searched the network with tfSimpleVASPs. In v2.1 discover takes the address in its body, and the network search takes the name in q.

V1address-ownerships, tfSimpleVASPs

HTTPHTTP
GET https://api.notabene.dev/v1/addresses/address-ownerships/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed?vasp_did=did:ethr:0x6c3b1f6a3e2d4c5b8a9e0f1d2c3b4a5968778695&asset=ETH
GET https://api.notabene.dev/tf/simple/vasps?q=Example%20Exchange

v2.1Discover the owner, or search the network

HTTPJSONHTTP
POST /api/v1/wallet-ownerships/discover

{
  "address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
}
{
  "source": "AddressHash",
  "owner": "did:web:beneficiary.example:pt",
  "custodian": "did:web:beneficiary.example:pt"
}
GET /api/v1/network/entities?q=Example Exchange
GET https://api.notabene.dev/v1/addresses/address-ownerships/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed?vasp_did=did:ethr:0x6c3b1f6a3e2d4c5b8a9e0f1d2c3b4a5968778695&asset=ETH
GET https://api.notabene.dev/tf/simple/vasps?q=Example%20Exchange
POST /api/v1/wallet-ownerships/discover

{
  "address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
}
{
  "source": "AddressHash",
  "owner": "did:web:beneficiary.example:pt",
  "custodian": "did:web:beneficiary.example:pt"
}
GET /api/v1/network/entities?q=Example Exchange

Keep the owner, which is the institution holding the address. An address no institution registered answers 404 with address owner not found. Creating the transfer runs the same lookup, so you need these calls only to show the answer before the transfer exists.

Withdrawals

The transfer names your registered customer by id and carries only the beneficiary’s data. Notabene finds the counterparty institution from the address, so you name it only when your customer picked it.

V1txCreate

HTTP
POST https://api.notabene.dev/tx/create

{
  "transactionRef": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40",
  "transactionAsset": "ETH",
  "transactionAmount": "500000000000000000",
  "originatorVASPdid": "did:ethr:0x6c3b1f6a3e2d4c5b8a9e0f1d2c3b4a5968778695",
  "beneficiaryVASPdid": "did:ethr:0xb0e2c1d4a3f5e6d7c8b9a0f1e2d3c4b5a6978877",
  "transactionBlockchainInfo": {
    "origin": "0xa40dfee99e1c85dc97fdc594b16a460717838703",
    "destination": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
  },
  "originator": {
    "originatorPersons": [
      {
        "naturalPerson": {
          "name": [
            {
              "nameIdentifier": [
                {
                  "primaryIdentifier": "Silva",
                  "secondaryIdentifier": "Ana",
                  "nameIdentifierType": "LEGL"
                }
              ]
            }
          ]
        }
      }
    ],
    "accountNumber": [
      "acc-ana-001"
    ]
  },
  "beneficiary": {
    "beneficiaryPersons": [
      {
        "naturalPerson": {
          "name": [
            {
              "nameIdentifier": [
                {
                  "primaryIdentifier": "Smith",
                  "secondaryIdentifier": "Alice",
                  "nameIdentifierType": "LEGL"
                }
              ]
            }
          ]
        }
      }
    ],
    "accountNumber": [
      "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
    ]
  }
}

v2.1Create the transfer

HTTPJSON
POST /api/v1/transfers

{
  "ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40",
  "asset": "ETH",
  "amount": "0.5",
  "settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "originator_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
  "beneficiary_id": "urn:uuid:e7d14b39-8a2c-4f05-b6e1-2c9f7a3d5b18",
  "beneficiary": {
    "ivms": {
      "naturalPerson": {
        "name": {
          "nameIdentifier": [
            {
              "primaryIdentifier": "Smith",
              "secondaryIdentifier": "Alice",
              "naturalPersonNameIdentifierType": "LEGL"
            }
          ]
        }
      }
    }
  }
}
{
  "transferId": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
  "status": "Active"
}
POST https://api.notabene.dev/tx/create

{
  "transactionRef": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40",
  "transactionAsset": "ETH",
  "transactionAmount": "500000000000000000",
  "originatorVASPdid": "did:ethr:0x6c3b1f6a3e2d4c5b8a9e0f1d2c3b4a5968778695",
  "beneficiaryVASPdid": "did:ethr:0xb0e2c1d4a3f5e6d7c8b9a0f1e2d3c4b5a6978877",
  "transactionBlockchainInfo": {
    "origin": "0xa40dfee99e1c85dc97fdc594b16a460717838703",
    "destination": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
  },
  "originator": {
    "originatorPersons": [
      {
        "naturalPerson": {
          "name": [
            {
              "nameIdentifier": [
                {
                  "primaryIdentifier": "Silva",
                  "secondaryIdentifier": "Ana",
                  "nameIdentifierType": "LEGL"
                }
              ]
            }
          ]
        }
      }
    ],
    "accountNumber": [
      "acc-ana-001"
    ]
  },
  "beneficiary": {
    "beneficiaryPersons": [
      {
        "naturalPerson": {
          "name": [
            {
              "nameIdentifier": [
                {
                  "primaryIdentifier": "Smith",
                  "secondaryIdentifier": "Alice",
                  "nameIdentifierType": "LEGL"
                }
              ]
            }
          ]
        }
      }
    ],
    "accountNumber": [
      "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
    ]
  }
}
POST /api/v1/transfers

{
  "ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40",
  "asset": "ETH",
  "amount": "0.5",
  "settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "originator_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
  "beneficiary_id": "urn:uuid:e7d14b39-8a2c-4f05-b6e1-2c9f7a3d5b18",
  "beneficiary": {
    "ivms": {
      "naturalPerson": {
        "name": {
          "nameIdentifier": [
            {
              "primaryIdentifier": "Smith",
              "secondaryIdentifier": "Alice",
              "naturalPersonNameIdentifierType": "LEGL"
            }
          ]
        }
      }
    }
  }
}
{
  "transferId": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
  "status": "Active"
}

Keep the transferId from the response, because every later call takes it in its path.

V1 fieldv2.1 fieldWhat changes
transactionRefref Required string, UUIDRequired. Notabene never creates two transfers with the same ref, so a retry is safe.
transactionAssetasset Required stringA symbol such as ETH, or a CAIP-19 identifier. Assets lists them.
transactionAmountamount Required string, decimalWhole units, so "0.5" where V1 took 500000000000000000 wei.
transactionAssetDecimalsNoneNot sent, because the amount is in whole units.
transactionBlockchainInfo.destinationsettlement_address Required stringCAIP-10 or a bare address. Notabene finds the counterparty institution from it.
transactionBlockchainInfo.originsource_address Optional stringThe address the funds leave from.
originatorVASPdidNoneYour institution comes from your access token.
beneficiaryVASPdidbeneficiary_entity_did Optional string, DIDOnly when your customer picked the counterparty institution themselves.
originator.originatorPersonsoriginator_id Required stringThe id of the customer you registered.
beneficiary.beneficiaryPersonsbeneficiary_id Required and beneficiary.ivmsAn id of your choosing, and the beneficiary’s name as IVMS-101 when the Travel Rule asks for it.
beneficiaryProofbeneficiary_proof_of_ownership OptionalThe proof for your customer’s own self-hosted wallet, as Wallets describes.
V1 fieldv2.1 fieldWhat changes
transactionRefref (required), string, UUIDRequired. Notabene never creates two transfers with the same ref, so a retry is safe.
transactionAssetasset (required), stringA symbol such as ETH, or a CAIP-19 identifier. Assets lists them.
transactionAmountamount (required), string, decimalWhole units, so "0.5" where V1 took 500000000000000000 wei.
transactionAssetDecimalsNoneNot sent, because the amount is in whole units.
transactionBlockchainInfo.destinationsettlement_address (required), stringCAIP-10 or a bare address. Notabene finds the counterparty institution from it.
transactionBlockchainInfo.originsource_address (optional), stringThe address the funds leave from.
originatorVASPdidNoneYour institution comes from your access token.
beneficiaryVASPdidbeneficiary_entity_did (optional), string, DIDOnly when your customer picked the counterparty institution themselves.
originator.originatorPersonsoriginator_id (required), stringThe id of the customer you registered.
beneficiary.beneficiaryPersonsbeneficiary_id (required) and beneficiary.ivmsAn id of your choosing, and the beneficiary’s name as IVMS-101 when the Travel Rule asks for it.
beneficiaryProofbeneficiary_proof_of_ownership (optional)The proof for your customer’s own self-hosted wallet, as Wallets describes.

Withdrawals has the whole flow, with its errors.

Approving and declining

In V1, your backend approved an outgoing transfer with txApprove, and accepted or declined an incoming one with txAccept or txDecline. In v2.1 your policies approve, flag or reject each transfer within seconds, and there is no call when they approve it.

A flagged transfer waits for a person on your team, who decides in the dashboard Inbox or through the API. Approving overrides your policies’ verdict, so it needs a reason, which stays on the transfer’s timeline.

V1txApprove, txAccept, txDecline

HTTPHTTP
POST https://api.notabene.dev/tx/approve?id=V1_TRANSFER_ID
POST https://api.notabene.dev/tx/decline

{
  "id": "V1_TRANSFER_ID",
  "reason": "Destination not allowed"
}

v2.1Decide on a flagged transfer

HTTPHTTP
POST /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/force-authorize

{
  "reason": "Reviewed by compliance"
}
POST /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/reject

{
  "reason": "Destination not allowed"
}
POST https://api.notabene.dev/tx/approve?id=V1_TRANSFER_ID
POST https://api.notabene.dev/tx/decline

{
  "id": "V1_TRANSFER_ID",
  "reason": "Destination not allowed"
}
POST /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/force-authorize

{
  "reason": "Reviewed by compliance"
}
POST /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/reject

{
  "reason": "Destination not allowed"
}

Approving answers 204, and rejecting answers 200 with the transfer, now Rejected. The same two calls work for withdrawals and deposits.

Canceling a withdrawal

When your customer cancels a withdrawal before it is settled, POST /api/v1/transfers/{id}/cancel replaces txCancel. Only the institution that created the transfer can cancel it, and the reason is shared with the counterparty.

V1txCancel

HTTP
POST https://api.notabene.dev/tx/cancel

{
  "id": "V1_TRANSFER_ID",
  "reason": "Canceled by the customer"
}

v2.1Cancel the transfer

HTTP
POST /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/cancel

{
  "reason": "Canceled by the customer"
}
POST https://api.notabene.dev/tx/cancel

{
  "id": "V1_TRANSFER_ID",
  "reason": "Canceled by the customer"
}
POST /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/cancel

{
  "reason": "Canceled by the customer"
}

The call answers 200 with the transfer, now Canceled, which Statuses describes.

Transaction hash

In V1 you added the hash to the transfer with txUpdate. In v2.1 it has its own call, made once every institution has authorized the transfer, which transfer.agent_status_changed.v3 tells you.

V1txUpdate

HTTP
POST https://api.notabene.dev/tx/update

{
  "id": "V1_TRANSFER_ID",
  "txHash": "0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d"
}

v2.1Settle the transfer

HTTPJSON
POST /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/settle

{
  "settlement_id": "0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d"
}
{
  "id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
  "status": "Settled",
  "settlement_id": "0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "direction": "Outgoing"
}
POST https://api.notabene.dev/tx/update

{
  "id": "V1_TRANSFER_ID",
  "txHash": "0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d"
}
POST /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/settle

{
  "settlement_id": "0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d"
}
{
  "id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
  "status": "Settled",
  "settlement_id": "0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "direction": "Outgoing"
}

Recording the same hash again answers 200, while a different hash answers 409.

Address confirmation

In V1, every incoming transfer waited for txConfirm or txReject. In v2.1 Notabene confirms an address you registered, so with your deposit addresses registered you rarely answer at all.

When Notabene cannot match the address, requirements.pending.v3 asks you about it. Register the address, confirm it for this transfer only, or reject it.

V1txConfirm, txReject

HTTPHTTP
POST https://api.notabene.dev/tx/confirm?id=V1_TRANSFER_ID
POST https://api.notabene.dev/tx/reject

{
  "id": "V1_TRANSFER_ID",
  "reason": "Address not ours"
}

v2.1Answer the address requirement

HTTPHTTPHTTP
POST /api/v1/wallets

{
  "address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "customer_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
}
POST /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/wallet-address-confirmation/resolve

{
  "outcome": "confirmed"
}
POST /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/wallet-address-confirmation/resolve

{
  "outcome": "rejected"
}
POST https://api.notabene.dev/tx/confirm?id=V1_TRANSFER_ID
POST https://api.notabene.dev/tx/reject

{
  "id": "V1_TRANSFER_ID",
  "reason": "Address not ours"
}
POST /api/v1/wallets

{
  "address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "customer_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
}
POST /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/wallet-address-confirmation/resolve

{
  "outcome": "confirmed"
}
POST /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/wallet-address-confirmation/resolve

{
  "outcome": "rejected"
}

Registering the address answers every waiting transfer to it, and later ones too. Rejecting it rejects the transfer, and the counterparty stops treating the address as yours. Deposits, Travel Rule data first has the whole deposit flow.

Deposits without Travel Rule data

V1 used txNotify to tell the sender about a deposit that came without its Travel Rule data. In v2.1 the data usually arrives before the funds, so you first look the deposit up by its hash.

When nothing matches, you create the transfer as the beneficiary, with the hash. Naming the sending institution asks it for its data, and POST /api/v1/wallet-ownerships/discover finds that institution from the address the funds came from.

V1txNotify

HTTP
POST https://api.notabene.dev/tx/notify

{
  "txHash": "0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "transactionAmount": "500000000000000000",
  "transactionAsset": "ETH",
  "beneficiaryVASPdid": "did:ethr:0x6c3b1f6a3e2d4c5b8a9e0f1d2c3b4a5968778695",
  "destination": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "origin": "0xa40dfee99e1c85dc97fdc594b16a460717838703",
  "customerRef": "customer-123"
}

v2.1Look the deposit up, then ask for the data

HTTPHTTP
GET /api/v1/transfers/match?settlement_id=0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d&settlement_address=eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed
POST /api/v1/transfers

{
  "ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40",
  "asset": "ETH",
  "amount": "0.5",
  "source_address": "eip155:1:0xa40dfee99e1c85dc97fdc594b16a460717838703",
  "settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "settlement_id": "0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "originator_entity_did": "did:web:originator.example:pt",
  "beneficiary_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
}
POST https://api.notabene.dev/tx/notify

{
  "txHash": "0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "transactionAmount": "500000000000000000",
  "transactionAsset": "ETH",
  "beneficiaryVASPdid": "did:ethr:0x6c3b1f6a3e2d4c5b8a9e0f1d2c3b4a5968778695",
  "destination": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "origin": "0xa40dfee99e1c85dc97fdc594b16a460717838703",
  "customerRef": "customer-123"
}
GET /api/v1/transfers/match?settlement_id=0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d&settlement_address=eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed
POST /api/v1/transfers

{
  "ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40",
  "asset": "ETH",
  "amount": "0.5",
  "source_address": "eip155:1:0xa40dfee99e1c85dc97fdc594b16a460717838703",
  "settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "settlement_id": "0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "originator_entity_did": "did:web:originator.example:pt",
  "beneficiary_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
}

When the lookup finds the transfer, credit your customer once it is Settled.

Completing a deposit

In V1 you completed a deposit’s data with txUpdate, naming the sending institution, marking the wallet as self-hosted or adding the sender yourself. In v2.1 the sending institution goes on the create above, so only the last two cases take a call of their own.

For a deposit from a self-hosted wallet, or one whose sender no institution claims, create the transfer with your own DID as the beneficiary’s institution. Then add the sender your customer declared, using the originator.id that GET /api/v1/transfers/{id} shows.

V1txUpdate

HTTPHTTP
POST https://api.notabene.dev/tx/update

{
  "id": "V1_TRANSFER_ID",
  "originatorVASPdid": "did:ethr:0xb0e2c1d4a3f5e6d7c8b9a0f1e2d3c4b5a6978877"
}
POST https://api.notabene.dev/tx/update

{
  "id": "V1_TRANSFER_ID",
  "isNonCustodial": true,
  "originator": {
    "originatorPersons": [
      {
        "naturalPerson": {
          "name": [
            {
              "nameIdentifier": [
                {
                  "primaryIdentifier": "Silva",
                  "secondaryIdentifier": "Ana",
                  "nameIdentifierType": "LEGL"
                }
              ]
            }
          ]
        }
      }
    ],
    "accountNumber": [
      "0xa40dfee99e1c85dc97fdc594b16a460717838703"
    ]
  }
}

v2.1Create the transfer, then add the sender

HTTPHTTP
POST /api/v1/transfers

{
  "ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40",
  "asset": "ETH",
  "amount": "0.5",
  "source_address": "eip155:1:0xa40dfee99e1c85dc97fdc594b16a460717838703",
  "settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "settlement_id": "0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "beneficiary_entity_did": "did:web:yourexchange.example:pt",
  "beneficiary_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
}
PATCH /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/parties/originator

{
  "party": {
    "id": "urn:uuid:9a41c7e2-6b3d-4f58-8e1a-2d7c5b9f0e36",
    "ivms": {
      "naturalPerson": {
        "name": {
          "nameIdentifier": [
            {
              "primaryIdentifier": "Silva",
              "secondaryIdentifier": "Ana",
              "naturalPersonNameIdentifierType": "LEGL"
            }
          ]
        },
        "dateAndPlaceOfBirth": {
          "dateOfBirth": "1988-03-02",
          "placeOfBirth": "Porto"
        }
      }
    }
  },
  "reason": "Declared by our customer"
}
POST https://api.notabene.dev/tx/update

{
  "id": "V1_TRANSFER_ID",
  "originatorVASPdid": "did:ethr:0xb0e2c1d4a3f5e6d7c8b9a0f1e2d3c4b5a6978877"
}
POST https://api.notabene.dev/tx/update

{
  "id": "V1_TRANSFER_ID",
  "isNonCustodial": true,
  "originator": {
    "originatorPersons": [
      {
        "naturalPerson": {
          "name": [
            {
              "nameIdentifier": [
                {
                  "primaryIdentifier": "Silva",
                  "secondaryIdentifier": "Ana",
                  "nameIdentifierType": "LEGL"
                }
              ]
            }
          ]
        }
      }
    ],
    "accountNumber": [
      "0xa40dfee99e1c85dc97fdc594b16a460717838703"
    ]
  }
}
POST /api/v1/transfers

{
  "ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40",
  "asset": "ETH",
  "amount": "0.5",
  "source_address": "eip155:1:0xa40dfee99e1c85dc97fdc594b16a460717838703",
  "settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "settlement_id": "0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "beneficiary_entity_did": "did:web:yourexchange.example:pt",
  "beneficiary_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
}
PATCH /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/parties/originator

{
  "party": {
    "id": "urn:uuid:9a41c7e2-6b3d-4f58-8e1a-2d7c5b9f0e36",
    "ivms": {
      "naturalPerson": {
        "name": {
          "nameIdentifier": [
            {
              "primaryIdentifier": "Silva",
              "secondaryIdentifier": "Ana",
              "naturalPersonNameIdentifierType": "LEGL"
            }
          ]
        },
        "dateAndPlaceOfBirth": {
          "dateOfBirth": "1988-03-02",
          "placeOfBirth": "Porto"
        }
      }
    }
  },
  "reason": "Declared by our customer"
}

Adding the sender answers 204, and the one call puts everything you collected about the sender on the transfer. If you know only the name, send name in place of ivms. Deposits, funds first has the whole flow, with its errors.

Redirecting a transfer

When your group holds institutions in several jurisdictions, V1 routed an incoming transfer from one to another with txRedirect. In v2.1 your institution on the transfer replaces itself with the institution that should handle it.

V1txRedirect

HTTP
POST https://api.notabene.dev/tx/redirect?id=V1_TRANSFER_ID&to=did:ethr:0x2f4e6a8c0b1d3f5a7c9e1b3d5f7a9c0e2b4d6f81

v2.1Replace your institution on the transfer

HTTP
POST /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/agents/replace

{
  "original": "did:web:yourexchange.example:pt",
  "replacement": {
    "id": "did:web:yourexchange.example:fr"
  }
}
POST https://api.notabene.dev/tx/redirect?id=V1_TRANSFER_ID&to=did:ethr:0x2f4e6a8c0b1d3f5a7c9e1b3d5f7a9c0e2b4d6f81
POST /api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/agents/replace

{
  "original": "did:web:yourexchange.example:pt",
  "replacement": {
    "id": "did:web:yourexchange.example:fr"
  }
}

The replacement keeps your institution’s role and the customer it acts for, and the call answers 200 with the transfer. An institution can replace only itself, and once replaced it can take no further action on the transfer.

Reading transfers

GET /api/v1/transfers/{id} replaces txInfo, and GET /api/v1/transfers replaces txList, without the vaspDID that V1 asked for. A transfer has its own status, and each institution on it has one too, as Statuses explains.

Webhooks

Add your endpoint in the dashboard, under Developers › Webhooks, rather than through the API. It receives four events: transfer.created.v3, transfer.status_changed.v3, transfer.agent_status_changed.v3 and requirements.pending.v3.

Each delivery is signed, so verify it with your endpoint’s signing secret. Deliveries can repeat or arrive out of order, as Webhooks describes.

Common mistakes

MistakeWhat happensInstead
The amount in the smallest unit, as in V1500000000000000000 is read as that many ETH.Send "0.5" for 0.5 ETH.
A bare address when you register itPOST/api/v1/wallets answers 400.Send it as CAIP-10.
IVMS-101 names in the V1 shapeThe request fails validation.Send name as an object, with naturalPersonNameIdentifierType.
An inline beneficiary without beneficiary_id400 with party profile provided without party ID.Send beneficiary_id with every inline beneficiary.
Your V1 credential or audienceThe token request fails.Use the credential and audience of your v2.1 account.
MistakeWhat happensInstead
The amount in the smallest unit, as in V1500000000000000000 is read as that many ETH.Send "0.5" for 0.5 ETH.
A bare address when you register itPOST /api/v1/wallets answers 400.Send it as CAIP-10.
IVMS-101 names in the V1 shapeThe request fails validation.Send name as an object, with naturalPersonNameIdentifierType.
An inline beneficiary without beneficiary_id400 with party profile provided without party ID.Send beneficiary_id with every inline beneficiary.
Your V1 credential or audienceThe token request fails.Use the credential and audience of your v2.1 account.

Go live lists the checks to make in sandbox before you move production traffic.


Did this page help you?