Wallets

Your wallets are the blockchain addresses you hold for your customers. Once you register them with Notabene, a counterparty institution sending funds to one finds you from the address, and Notabene confirms it is yours without asking you.

Why register your addresses

When a counterparty institution creates a transfer to an address you registered, Notabene finds you as its owner. The transfer then reaches you even when the sender does not know which institution you are. When the transfer arrives, Notabene confirms by itself that the address is yours, and the sender’s personal data comes through without waiting for you.

For an address you did not register, you are asked to confirm it first. Registering it then, with its customer, confirms it on that transfer and on any other open transfer to it, as described in Deposits, Travel Rule data first.

In the dashboard, My Organization › My Wallet Addresses lists every address you hold, the ones you registered and the ones you confirmed on a transfer.

Sandbox and production each keep their own directory of addresses, so the addresses you registered in sandbox are unknown in production. Register them again there.

Register an address

Register each address with POST /api/v1/wallets, together with the customer it belongs to, after registering that customer as described in Customers.

API reference
curl https://sandbox.notabene.id/api/v1/wallets \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
    "customer_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
    "managed": true
  }'const response = await fetch('https://sandbox.notabene.id/api/v1/wallets', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
    "customer_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
    "managed": true
  }),
});
const answer = response.status === 204 ? null : await response.json();import requests

response = requests.post(
    'https://sandbox.notabene.id/api/v1/wallets',
    headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
    json={
      "address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
      "customer_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
      "managed": True
    },
)
answer = None if response.status_code == 204 else response.json()
What to replace
  • eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed: Your customer’s address, as CAIP-10
  • urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27: The id of the customer the address belongs to
Response201
{
  "id": "did:pkh:eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "chain": "eip155:1",
  "customer_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
  "created_at": "2026-10-16T09:20:11Z"
}
curl https://sandbox.notabene.id/api/v1/wallets \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
    "customer_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
    "managed": true
  }'
const response = await fetch('https://sandbox.notabene.id/api/v1/wallets', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
    "customer_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
    "managed": true
  }),
});
const answer = response.status === 204 ? null : await response.json();
import requests

response = requests.post(
    'https://sandbox.notabene.id/api/v1/wallets',
    headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
    json={
      "address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
      "customer_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
      "managed": True
    },
)
answer = None if response.status_code == 204 else response.json()
{
  "id": "did:pkh:eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "chain": "eip155:1",
  "customer_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
  "created_at": "2026-10-16T09:20:11Z"
}

The wallet record’s id is the address written as a did:pkh. Registering the same address again with the same details returns the same record, so a retry is safe, while other details, such as another customer, answer 409 with a wallet with this address is already registered with different content. To change the details of an address, remove it and register it again.

FieldWhat to send
address Required string, CAIP-10The address as a CAIP-10 identifier, which is the blockchain’s CAIP-2 identifier followed by the address, for example eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed on Ethereum or eip155:137:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed on Polygon. A bare address answers 400.
customer_id Optional stringThe id of the customer the address belongs to. Leave it out for an omnibus wallet that holds funds for many customers, such as a hot wallet.
managed Optional booleantrue, the default, for an address you operate. false marks a self-hosted wallet, which Notabene does not publish to counterparty institutions.
custodian Optional string, DIDThe DID of the wallet provider that holds the address for you, if you use one. It cannot be set when managed is false.
FieldWhat to send
address (required), string, CAIP-10The address as a CAIP-10 identifier, which is the blockchain’s CAIP-2 identifier followed by the address, for example eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed on Ethereum or eip155:137:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed on Polygon. A bare address answers 400.
customer_id (optional), stringThe id of the customer the address belongs to. Leave it out for an omnibus wallet that holds funds for many customers, such as a hot wallet.
managed (optional), booleantrue, the default, for an address you operate. false marks a self-hosted wallet, which Notabene does not publish to counterparty institutions.
custodian (optional), string, DIDThe DID of the wallet provider that holds the address for you, if you use one. It cannot be set when managed is false.

What else registering an address with managed set to false changes is still to be confirmed. (placeholder)

Look up, list and remove

CallWhat it does
GET/api/v1/wallets/{address}Returns one address with its customer. It answers 404 with wallet not found when the address is not registered.
GET/api/v1/walletsLists your addresses one page at a time, under items. Pass limit, and cursor with the next_cursor of the previous page. The last page has no next_cursor.
DELETE/api/v1/wallets/{address}Removes the address and answers with { "deleted": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" }. Counterparty institutions no longer find you from it.
CallWhat it does
GET /api/v1/wallets/{address}Returns one address with its customer. It answers 404 with wallet not found when the address is not registered.
GET /api/v1/walletsLists your addresses one page at a time, under items. Pass limit, and cursor with the next_cursor of the previous page. The last page has no next_cursor.
DELETE /api/v1/wallets/{address}Removes the address and answers with { "deleted": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" }. Counterparty institutions no longer find you from it.

Addresses held by a wallet provider

If a wallet provider holds the address for you, give its DID in custodian when you register the address. A counterparty institution that looks the address up then finds you as the owner and your wallet provider as the custodian. When you hold the address yourself, you are both.

With a wallet-managed integration, where you select your wallet provider in the dashboard under Integrations, the provider registers your addresses for you. Integrations lists the wallet providers.

Find who holds an address

To find which institution holds an address, for example the address a deposit came from, send it to POST /api/v1/wallet-ownerships/discover. It takes a CAIP-10 identifier, a did:pkh or a bare EVM address.

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

response = requests.post(
    'https://sandbox.notabene.id/api/v1/wallet-ownerships/discover',
    headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
    json={
      "address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"
    },
)
answer = None if response.status_code == 204 else response.json()
What to replace
  • eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359: The address to look up, as CAIP-10, a did:pkh or a bare EVM address
Response200
{
  "owner": "did:web:originator.example:pt",
  "custodian": "did:web:wallet-provider.example",
  "source": "AddressHash"
}
curl https://sandbox.notabene.id/api/v1/wallet-ownerships/discover \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"
  }'
const response = await fetch('https://sandbox.notabene.id/api/v1/wallet-ownerships/discover', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"
  }),
});
const answer = response.status === 204 ? null : await response.json();
import requests

response = requests.post(
    'https://sandbox.notabene.id/api/v1/wallet-ownerships/discover',
    headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
    json={
      "address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"
    },
)
answer = None if response.status_code == 204 else response.json()
{
  "owner": "did:web:originator.example:pt",
  "custodian": "did:web:wallet-provider.example",
  "source": "AddressHash"
}

In the response, owner is the institution that owns the address, custodian the one that holds it, and source how the ownership was established. AddressHash means the owner registered the address with Notabene. When no institution has registered it, the call answers 404 with address owner not found, and you ask your customer instead, as described in Deposits, funds first.

Self-hosted wallets

A self-hosted wallet is a wallet your customer holds the keys to themselves, with no institution behind it. When your customer sends funds to their own self-hosted wallet, or receives funds from it, record proof that the wallet is theirs with POST /api/v1/customers/{customer}/wallets/{address}/proofs.

API reference
curl https://sandbox.notabene.id/api/v1/customers/urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27/wallets/eip155:1:0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB/proofs \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "type": "self-declaration",
    "attestation": "I hold the keys to this wallet and use it for my own funds."
  }'const response = await fetch('https://sandbox.notabene.id/api/v1/customers/urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27/wallets/eip155:1:0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB/proofs', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "type": "self-declaration",
    "attestation": "I hold the keys to this wallet and use it for my own funds."
  }),
});
const answer = response.status === 204 ? null : await response.json();import requests

response = requests.post(
    'https://sandbox.notabene.id/api/v1/customers/urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27/wallets/eip155:1:0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB/proofs',
    headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
    json={
      "type": "self-declaration",
      "attestation": "I hold the keys to this wallet and use it for my own funds."
    },
)
answer = None if response.status_code == 204 else response.json()
What to replace
  • urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27: The id of your customer who holds the wallet
  • eip155:1:0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB: The self-hosted wallet’s address, as CAIP-10
  • I hold the keys to this wallet and use it for my own funds.: Your customer’s statement, as text
Response201
{
  "id": "3f2b7c1e-8a4d-4e6f-9b0a-5c1d2e3f4a5b",
  "customer": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
  "address": "eip155:1:0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
  "type": "self-declaration",
  "status": "Submitted",
  "created_at": "2026-10-16T09:24:37Z"
}
curl https://sandbox.notabene.id/api/v1/customers/urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27/wallets/eip155:1:0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB/proofs \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "type": "self-declaration",
    "attestation": "I hold the keys to this wallet and use it for my own funds."
  }'
const response = await fetch('https://sandbox.notabene.id/api/v1/customers/urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27/wallets/eip155:1:0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB/proofs', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "type": "self-declaration",
    "attestation": "I hold the keys to this wallet and use it for my own funds."
  }),
});
const answer = response.status === 204 ? null : await response.json();
import requests

response = requests.post(
    'https://sandbox.notabene.id/api/v1/customers/urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27/wallets/eip155:1:0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB/proofs',
    headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
    json={
      "type": "self-declaration",
      "attestation": "I hold the keys to this wallet and use it for my own funds."
    },
)
answer = None if response.status_code == 204 else response.json()
{
  "id": "3f2b7c1e-8a4d-4e6f-9b0a-5c1d2e3f4a5b",
  "customer": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
  "address": "eip155:1:0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
  "type": "self-declaration",
  "status": "Submitted",
  "created_at": "2026-10-16T09:24:37Z"
}

The proof type says how your customer proves the wallet is theirs.

TypeWhat to sendWhat happens next
eip-191 for Ethereum and other EVM blockchains, bip-322 for Bitcoinsignature, a message signed with the wallet’s key.The signature is checked when you submit it, so it needs no review. A signature that does not match the address is recorded as Failed.
self-declarationattestation, your customer’s statement as text.The proof stays Submitted until your compliance team accepts or rejects it.
screenshotattestation, an object with the url of the screenshot, as in { "url": "https://example.com/wallet-screenshot.png" }.The proof stays Submitted until your compliance team accepts or rejects it.
TypeWhat to sendWhat happens next
eip-191 for Ethereum and other EVM blockchains, bip-322 for Bitcoinsignature, a message signed with the wallet’s key.The signature is checked when you submit it, so it needs no review. A signature that does not match the address is recorded as Failed.
self-declarationattestation, your customer’s statement as text.The proof stays Submitted until your compliance team accepts or rejects it.
screenshotattestation, an object with the url of the screenshot, as in { "url": "https://example.com/wallet-screenshot.png" }.The proof stays Submitted until your compliance team accepts or rejects it.

The message your customer signs is still to be confirmed. (placeholder)

You can add wallet_provider, the wallet app that produced the proof, such as MetaMask, and the bip-137-xpub type also takes the wallet’s extended public key in xpub. An unknown type answers 400.

Reviewing a proof

Your compliance team accepts a proof with POST /api/v1/wallet-proofs/{id}/accept, which sets its status to Accepted, or rejects it with POST /api/v1/wallet-proofs/{id}/reject, which sets it to Failed. A proof can only be decided once, so deciding it again answers 409 with proof is already decided.

Each new proof for an address replaces the one before it, which can then no longer be decided. GET /api/v1/customers/{customer}/wallets/{address}/proofs lists every proof recorded for the address, with its status.

Sending the proof with the transfer

You can also send the proof with the transfer, with the same fields: originator_proof_of_ownership when the funds leave a self-hosted source_address, and beneficiary_proof_of_ownership when they go to a self-hosted settlement_address.

Wallets and your policies

The addresses you hold carry no rules of their own. Your policies look at self-hosted wallets and at the addresses counterparty institutions hold, each in its own tab under Network › Wallet Addresses in the dashboard.

For a self-hosted wallet, your self-hosted wallet rules use its proof, shown as Verified or Unverified. A rule for Verified proofs can approve or flag the transfer, and a rule for Unverified proofs can flag or reject it. When your team approves a transfer that carries the address’s current proof, the proof counts as Verified from then on. How the API statuses Submitted, Accepted and Failed correspond to Verified and Unverified is still to be confirmed. (placeholder)

For an address a counterparty institution holds, connect a risk-screening provider such as Chainalysis or Elliptic under Integrations. The address then gets a risk score at every transfer that involves it, and your counterparty wallet rules turn that score into a verdict.


Did this page help you?