Deposits, Travel Rule data first
When a counterparty institution sends funds to one of your customers, its Travel Rule data usually reaches you before the funds do. Notabene receives the transfer for you, checks the deposit address, receives the sender’s data and runs your policies. Once the funds land, you match them to the transfer and credit your customer.
Most deposits take one call from you, the lookup that matches the funds to their transfer. You answer Notabene in steps 2 to 4 only when it asks you something.
Done by Notabene
On the counterparty’s side
Only when needed
A counterparty institution creates a transfer to the address of one of your customers, and your webhook endpoint is told. The event and the call that reads the transfer are in 1. Receive the transfer.
Notabene checks that the deposit address belongs to you, receives the sender’s personal data once the address is confirmed, and runs your policies on the transfer.
- When you registered the address, or confirmed it on an earlier transfer, Notabene confirms it by itself.
- If the sender never sends its data, the requirement to receive it is skipped after its time-out and the skip is recorded in your audit trail. The time-out is 1 minute with either requirements preset.
Only when Notabene cannot match the address to you. Registering it with its customer confirms it, and rejecting it tells the sender it is not yours. The calls are in 2. If Notabene cannot match the address, register it.
Only if you turned on beneficiary name matching, which works with either requirements preset. The call and its answers are in 3. If you match names, report the result.
When the rules you configured approve the transfer and nothing else is open on your side, your side is authorized automatically, and there is no call to make.
When your policies flag the transfer, a person on your team approves or rejects it, in the dashboard Inbox, or through the API. The calls are in 4. If your policies flag it, review the transfer.
- Rejecting tells the sending institution, with your reason.
The sending institution broadcasts the funds on chain and records the transaction hash on the transfer.
- You do not record the settlement yourself: the sender does.
When the funds land, look up their transfer by the transaction hash, and retry for a while if nothing matches yet. The call is in 5. Match the deposit, then credit your customer.
Once the lookup returns the transfer as Settled, credit your customer.
- A transfer arrives for your customer (
transfer.created.v3, Notabene, to your webhook endpoint). A counterparty institution creates a transfer to the address of one of your customers, and your webhook endpoint is told. The event and the call that reads the transfer are in 1. Receive the transfer. - Notabene checks the address and the data (Notabene, as the transfer arrives). Notabene checks that the deposit address belongs to you, receives the sender’s personal data once the address is confirmed, and runs your policies on the transfer. Matches the address to you; Receives the Travel Rule data; Runs your policies; Asks you only when needed. When you registered the address, or confirmed it on an earlier transfer, Notabene confirms it by itself. If the sender never sends its data, the requirement to receive it is skipped after its time-out and the skip is recorded in your audit trail. The time-out is 1 minute with either requirements preset.
- Register the deposit address (only when needed, Your backend). Only when Notabene cannot match the address to you. Registering it with its customer confirms it, and rejecting it tells the sender it is not yours. The calls are in 2. If Notabene cannot match the address, register it.
- Report the name check (only when needed, Your backend or your team). Only if you turned on beneficiary name matching, which works with either requirements preset. The call and its answers are in 3. If you match names, report the result.
- Automatic: Your policies approve it automatically (Your policy engine, with the rules you configured). When the rules you configured approve the transfer and nothing else is open on your side, your side is authorized automatically, and there is no call to make.
- Manual review: Your team reviews a flagged transfer (Your compliance team). When your policies flag the transfer, a person on your team approves or rejects it, in the dashboard Inbox, or through the API. The calls are in 4. If your policies flag it, review the transfer. Rejecting tells the sending institution, with your reason.
- The sender settles the transfer (The sending institution, in its own systems). The sending institution broadcasts the funds on chain and records the transaction hash on the transfer. You do not record the settlement yourself: the sender does.
- Match the deposit to its transfer (
GET /api/v1/transfers/match, Your backend, for every deposit your wallet receives). When the funds land, look up their transfer by the transaction hash, and retry for a while if nothing matches yet. The call is in 5. Match the deposit, then credit your customer. - Credit your customer (Your backend). Once the lookup returns the transfer as
Settled, credit your customer.
Before you start
- An access token, for reading the transfer and answering what it asks of you, as described in Authentication.
- Your customers and their deposit addresses registered with Notabene, as described in Customers and Wallets, so that step 2 never comes up.
- A webhook endpoint, as described in Webhooks.
1. Receive the transfer
When the sender creates the transfer, your webhook endpoint receives transfer.created.v3, with direction set to Incoming. The ref belongs to the sender, so it is empty on your copy.
{
"eventType": "transfer.created.v3",
"payload": {
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"for": "did:web:beneficiary.example:pt",
"from": "did:web:originator.example:pt",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt",
"asset": "eip155:1/slip44:60",
"amount": "0.5",
"ref": ""
}
}
{
"eventType": "transfer.created.v3",
"payload": {
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"for": "did:web:beneficiary.example:pt",
"from": "did:web:originator.example:pt",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt",
"asset": "eip155:1/slip44:60",
"amount": "0.5",
"ref": ""
}
}Read the transfer with include_ivms=true to see the sender’s personal data, which appears under originator.ivms once the deposit address is confirmed. Until then, originator holds only an id, and requires_action is true while something waits on you. next_action.type reads ConfirmRelationship while the sender waits for you to confirm the address, and Statuses and decisions in the API lists the other values.
curl --get https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80 \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --data-urlencode 'include_ivms=true'const url = new URL('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80'); url.searchParams.set('include_ivms', 'true'); 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/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80', headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}, params={ "include_ivms": "true" }, ) answer = None if response.status_code == 204 else response.json()
What to replace
8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80: The transfer’s id, from the webhook
{
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"status": "Active",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt",
"asset": "eip155:1/slip44:60",
"amount": "0.5",
"requires_action": true,
"next_action": {
"type": "Review"
},
"originator": {
"id": "urn:uuid:c4eaf254-57e0-49f2-b10a-985f497eb389",
"ivms": {
"naturalPerson": {
"name": {
"nameIdentifier": [
{
"primaryIdentifier": "Silva",
"secondaryIdentifier": "Ana",
"naturalPersonNameIdentifierType": "LEGL"
}
]
}
}
}
},
"beneficiary": {
"id": "urn:uuid:e7d14b39-8a2c-4f05-b6e1-2c9f7a3d5b18"
}
}curl --get https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80 \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--data-urlencode 'include_ivms=true'const url = new URL('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80');
url.searchParams.set('include_ivms', 'true');
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/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
params={
"include_ivms": "true"
},
)
answer = None if response.status_code == 204 else response.json(){
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"status": "Active",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt",
"asset": "eip155:1/slip44:60",
"amount": "0.5",
"requires_action": true,
"next_action": {
"type": "Review"
},
"originator": {
"id": "urn:uuid:c4eaf254-57e0-49f2-b10a-985f497eb389",
"ivms": {
"naturalPerson": {
"name": {
"nameIdentifier": [
{
"primaryIdentifier": "Silva",
"secondaryIdentifier": "Ana",
"naturalPersonNameIdentifierType": "LEGL"
}
]
}
}
}
},
"beneficiary": {
"id": "urn:uuid:e7d14b39-8a2c-4f05-b6e1-2c9f7a3d5b18"
}
}A transfer only reaches you once the sending institution’s policies have approved it. If the sender never sends its data, the requirement to receive it is skipped after 1 minute, and the skip is recorded in your audit trail.
2. If Notabene cannot match the address, register it
When Notabene cannot match the address, it sends requirements.pending.v3 with a WalletAddressConfirmation requirement whose agentId is your DID. The sender keeps its personal data until the address is confirmed.
Register the address with the customer it belongs to, and Notabene confirms it by itself within seconds, on this transfer and on any other open transfer to it. If the address is not yours, reject it instead.
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" }'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" }), }); 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" }, ) answer = None if response.status_code == 204 else response.json()
curl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/wallet-address-confirmation/resolve \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "outcome": "rejected" }'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/wallet-address-confirmation/resolve', { method: 'POST', headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN', 'Content-Type': 'application/json', }, body: JSON.stringify({ "outcome": "rejected" }), }); const answer = response.status === 204 ? null : await response.json();import requests response = requests.post( 'https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/wallet-address-confirmation/resolve', headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}, json={ "outcome": "rejected" }, ) answer = None if response.status_code == 204 else response.json()
What to replace
eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed: The deposit address, as CAIP-10urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27: The id of your customer who owns the address8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80: The transfer’s id, from the webhook
{
"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-20T09:14:03Z"
}HTTP/1.1 204 No Contentcurl 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"
}'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"
}),
});
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"
},
)
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-20T09:14:03Z"
}curl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/wallet-address-confirmation/resolve \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"outcome": "rejected"
}'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/wallet-address-confirmation/resolve', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"outcome": "rejected"
}),
});
const answer = response.status === 204 ? null : await response.json();import requests
response = requests.post(
'https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/wallet-address-confirmation/resolve',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
json={
"outcome": "rejected"
},
)
answer = None if response.status_code == 204 else response.json()HTTP/1.1 204 No ContentRejecting the address answers 204 and rejects the transfer. The sender is told that the address is not yours, so it stops treating it as yours.
3. If you match names, report the result
If you turned on beneficiary name matching, in the dashboard under Authorization Rules › Requirements, compare the beneficiary’s name with your customer’s once the sender’s data has arrived. Reporting the result answers 204.
curl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/beneficiary-name-match/resolve \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "outcome": "matched" }'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/beneficiary-name-match/resolve', { method: 'POST', headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN', 'Content-Type': 'application/json', }, body: JSON.stringify({ "outcome": "matched" }), }); const answer = response.status === 204 ? null : await response.json();import requests response = requests.post( 'https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/beneficiary-name-match/resolve', headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}, json={ "outcome": "matched" }, ) answer = None if response.status_code == 204 else response.json()
What to replace
8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80: The transfer’s id, from the webhookmatched: matched, partial_match or mismatch
HTTP/1.1 204 No Contentcurl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/beneficiary-name-match/resolve \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"outcome": "matched"
}'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/beneficiary-name-match/resolve', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"outcome": "matched"
}),
});
const answer = response.status === 204 ? null : await response.json();import requests
response = requests.post(
'https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/requirements/beneficiary-name-match/resolve',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
json={
"outcome": "matched"
},
)
answer = None if response.status_code == 204 else response.json()HTTP/1.1 204 No Content| Outcome | What happens |
|---|---|
matched | The check is satisfied, and your side goes on to your policies. |
partial_match | The transfer is held for a person on your team to review. |
mismatch | Your side rejects the transfer. |
| Outcome | What happens |
|---|---|
matched | The check is satisfied, and your side goes on to your policies. |
partial_match | The transfer is held for a person on your team to review. |
mismatch | Your side rejects the transfer. |
4. If your policies flag it, review the transfer
When your policies approve the transfer and nothing else is open on your side, your side is authorized automatically. When they flag it, a person on your team approves or rejects it, in the dashboard Inbox, or through the API.
curl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/force-authorize \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "reason": "Reviewed by compliance" }'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/force-authorize', { method: 'POST', headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN', 'Content-Type': 'application/json', }, body: JSON.stringify({ "reason": "Reviewed by compliance" }), }); const answer = response.status === 204 ? null : await response.json();import requests response = requests.post( 'https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/force-authorize', headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}, json={ "reason": "Reviewed by compliance" }, ) answer = None if response.status_code == 204 else response.json()
curl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/reject \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "reason": "Beneficiary account closed" }'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/reject', { method: 'POST', headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN', 'Content-Type': 'application/json', }, body: JSON.stringify({ "reason": "Beneficiary account closed" }), }); const answer = response.status === 204 ? null : await response.json();import requests response = requests.post( 'https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/reject', headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}, json={ "reason": "Beneficiary account closed" }, ) answer = None if response.status_code == 204 else response.json()
What to replace
8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80: The transfer’s id, from the webhookReviewed by compliance: Why your team approves it, kept on the transfer’s timelineBeneficiary account closed: Why your team rejects it, shared with the sender
{
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"status": "Rejected",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt"
}curl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/force-authorize \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"reason": "Reviewed by compliance"
}'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/force-authorize', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"reason": "Reviewed by compliance"
}),
});
const answer = response.status === 204 ? null : await response.json();import requests
response = requests.post(
'https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/force-authorize',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
json={
"reason": "Reviewed by compliance"
},
)
answer = None if response.status_code == 204 else response.json()curl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/reject \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"reason": "Beneficiary account closed"
}'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/reject', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"reason": "Beneficiary account closed"
}),
});
const answer = response.status === 204 ? null : await response.json();import requests
response = requests.post(
'https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/reject',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
json={
"reason": "Beneficiary account closed"
},
)
answer = None if response.status_code == 204 else response.json(){
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"status": "Rejected",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt"
}Both calls answer as they do on Withdrawals, and a rejection tells the sender your reason.
Until you change your general default, which is set in Policies, a new account sends every deposit to review as well.
5. Match the deposit, then credit your customer
For every deposit your wallet receives, call GET /api/v1/transfers/match with its transaction hash and the address it landed on. The sender records the hash only once it sees its withdrawal complete on chain, so the lookup can come back empty at first. Retry it with a back-off while nothing matches, and stop after a retry window you define.
curl --get https://sandbox.notabene.id/api/v1/transfers/match \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --data-urlencode 'settlement_id=eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d' \ --data-urlencode 'settlement_address=eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed'const url = new URL('https://sandbox.notabene.id/api/v1/transfers/match'); url.searchParams.set('settlement_id', 'eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d'); url.searchParams.set('settlement_address', 'eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed'); 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/transfers/match', headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}, params={ "settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d", "settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" }, ) answer = None if response.status_code == 204 else response.json()
What to replace
eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d: The deposit’s transaction hash, as CAIP-220 or a bare hash on the asset’s chaineip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed: Your customer’s address the funds landed on
{
"data": [],
"all_filters_applied": true,
"pagination": {
"total": 0,
"limit": 25
}
}{
"data": [
{
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"type": "Transfer",
"status": "Settled",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt",
"asset": "eip155:1/slip44:60",
"amount": "0.5"
}
],
"all_filters_applied": true,
"pagination": {
"total": 1,
"limit": 25
}
}curl --get https://sandbox.notabene.id/api/v1/transfers/match \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--data-urlencode 'settlement_id=eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d' \
--data-urlencode 'settlement_address=eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed'const url = new URL('https://sandbox.notabene.id/api/v1/transfers/match');
url.searchParams.set('settlement_id', 'eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d');
url.searchParams.set('settlement_address', 'eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed');
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/transfers/match',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
params={
"settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
"settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
},
)
answer = None if response.status_code == 204 else response.json(){
"data": [],
"all_filters_applied": true,
"pagination": {
"total": 0,
"limit": 25
}
}{
"data": [
{
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"type": "Transfer",
"status": "Settled",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt",
"asset": "eip155:1/slip44:60",
"amount": "0.5"
}
],
"all_filters_applied": true,
"pagination": {
"total": 1,
"limit": 25
}
}When the lookup returns the transfer and its status is Settled, credit your customer. If nothing has matched by the end of your retry window, the deposit came without Travel Rule data, as described in Deposits, funds first.
When a call fails
| Answer | What it means |
|---|---|
400 with expected required property reason to be present | Approving needs a reason. |
400 with cannot force-authorize a concluded tx | The transfer is already Settled, canceled or reverted, so there is nothing to approve. |
| 401 | The token is missing, malformed or expired. Authentication explains how to get a new one. |
| 409 on approve | The transfer’s compliance inputs changed while it was being approved. Send the request again. |
| Answer | What it means |
|---|---|
400 with expected required property reason to be present | Approving needs a reason. |
400 with cannot force-authorize a concluded tx | The transfer is already Settled, canceled or reverted, so there is nothing to approve. |
401 | The token is missing, malformed or expired. Authentication explains how to get a new one. |
409 on approve | The transfer’s compliance inputs changed while it was being approved. Send the request again. |
Common mistakes
| Mistake | What happens | Instead |
|---|---|---|
| Reading the sender’s data before the address is confirmed | The originator and the beneficiary carry no personal data yet, since the sender sends it only once your institution has confirmed the address. | Register the address in step 2, then read the transfer. |
Reading the transfer without include_ivms=true | The originator and the beneficiary come with their ids only. | Add include_ivms=true when you need their personal data. |
| Rejecting the address to reject the transfer | The sender also learns that the address is not yours, and stops treating it as yours. | Reject the address only when it is not your customer’s. Otherwise reject the transfer. |
| Mistake | What happens | Instead |
|---|---|---|
| Reading the sender’s data before the address is confirmed | The originator and the beneficiary carry no personal data yet, since the sender sends it only once your institution has confirmed the address. | Register the address in step 2, then read the transfer. |
Reading the transfer without include_ivms=true | The originator and the beneficiary come with their ids only. | Add include_ivms=true when you need their personal data. |
| Rejecting the address to reject the transfer | The sender also learns that the address is not yours, and stops treating it as yours. | Reject the address only when it is not your customer’s. Otherwise reject the transfer. |
Transfers, statuses and events
Updated 3 days ago
