Deposits, funds first
Sometimes the funds reach your customer’s address before any Travel Rule data. They may come from an institution that did not send it, or from a wallet with no institution behind it. In that case you create the transfer yourself from the deposit, and either ask the sending institution for its data or record the sender your customer tells you about.
Before you start
- An access token, as described in Authentication. The examples use the sandbox base URL.
- Your customer and the address the funds landed on, registered with Notabene, as described in Wallets.
- A webhook endpoint, as described in Webhooks, to learn when the transfer is settled.
1. Look for a transfer
First check that no transfer explains the deposit, as described in Deposits. The sender records the transaction hash only once it sees its withdrawal complete, so retry the lookup with a back-off 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
}
}If a transfer matches, the Travel Rule data came first, as described in Deposits, Travel Rule data first.
2. Find out who sent it
Ask Notabene which institution holds the address the funds came from.
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 the funds came from
{
"source": "AddressHash",
"owner": "did:web:originator.example:pt",
"custodian": "did:web:originator.example:pt"
}{
"title": "Not Found",
"status": 404,
"detail": "address owner not found"
}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(){
"source": "AddressHash",
"owner": "did:web:originator.example:pt",
"custodian": "did:web:originator.example:pt"
}{
"title": "Not Found",
"status": 404,
"detail": "address owner not found"
}Keep the owner: it is the institution you ask in step 3. An institution is found only when it registered the address with Notabene, so when nothing is found, ask your customer who sent the funds.
3. Create the transfer
Create the transfer as the beneficiary, with the deposit’s transaction hash. Name the sending institution when step 2 found one, or otherwise name your own DID as the beneficiary’s institution and add the sender in step 4.
curl https://sandbox.notabene.id/api/v1/transfers \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40", "asset": "ETH", "amount": "0.5", "source_address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", "settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", "settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d", "originator_entity_did": "did:web:originator.example:pt", "beneficiary_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27" }'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers', { method: 'POST', headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN', 'Content-Type': 'application/json', }, body: JSON.stringify({ "ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40", "asset": "ETH", "amount": "0.5", "source_address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", "settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", "settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d", "originator_entity_did": "did:web:originator.example:pt", "beneficiary_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/transfers', headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}, json={ "ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40", "asset": "ETH", "amount": "0.5", "source_address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", "settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", "settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d", "originator_entity_did": "did:web:originator.example:pt", "beneficiary_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 \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40", "asset": "ETH", "amount": "0.5", "source_address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", "settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", "settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d", "beneficiary_entity_did": "did:web:beneficiary.example:pt", "beneficiary_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27" }'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers', { method: 'POST', headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN', 'Content-Type': 'application/json', }, body: JSON.stringify({ "ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40", "asset": "ETH", "amount": "0.5", "source_address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", "settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", "settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d", "beneficiary_entity_did": "did:web:beneficiary.example:pt", "beneficiary_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/transfers', headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}, json={ "ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40", "asset": "ETH", "amount": "0.5", "source_address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", "settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", "settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d", "beneficiary_entity_did": "did:web:beneficiary.example:pt", "beneficiary_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27" }, ) answer = None if response.status_code == 204 else response.json()
What to replace
0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40: A new UUID of yours for each transfereip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359: The address the funds came fromeip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed: Your customer’s address the funds landed oneip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d: The deposit’s transaction hash, as CAIP-220 or a bare hash on the asset’s chaindid:web:originator.example:pt: The owner that step 2 foundurn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27: The id of your customer who received the fundsdid:web:beneficiary.example:pt: Your own DID
{
"transferId": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"status": "Active"
}curl https://sandbox.notabene.id/api/v1/transfers \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40",
"asset": "ETH",
"amount": "0.5",
"source_address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
"settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
"settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
"originator_entity_did": "did:web:originator.example:pt",
"beneficiary_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
}'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40",
"asset": "ETH",
"amount": "0.5",
"source_address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
"settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
"settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
"originator_entity_did": "did:web:originator.example:pt",
"beneficiary_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/transfers',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
json={
"ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40",
"asset": "ETH",
"amount": "0.5",
"source_address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
"settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
"settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
"originator_entity_did": "did:web:originator.example:pt",
"beneficiary_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 \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40",
"asset": "ETH",
"amount": "0.5",
"source_address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
"settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
"settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
"beneficiary_entity_did": "did:web:beneficiary.example:pt",
"beneficiary_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
}'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40",
"asset": "ETH",
"amount": "0.5",
"source_address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
"settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
"settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
"beneficiary_entity_did": "did:web:beneficiary.example:pt",
"beneficiary_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/transfers',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
json={
"ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40",
"asset": "ETH",
"amount": "0.5",
"source_address": "eip155:1:0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
"settlement_address": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
"settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
"beneficiary_entity_did": "did:web:beneficiary.example:pt",
"beneficiary_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27"
},
)
answer = None if response.status_code == 204 else response.json(){
"transferId": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"status": "Active"
}Keep the transferId: the next calls take it in their path. When you name an institution, Notabene sends it a request for the sender’s data once your policies approve the transfer.
If the institution does not answer, the requirement to receive its data is skipped after 1 minute with either requirements preset, and the transfer goes on without the data. You can still add the sender your customer declares, as in step 4.
| Field | What to send |
|---|---|
ref Required string, UUID | Your own reference for the transfer. Notabene never creates two transfers with the same ref, so a retry is safe. |
asset Required string | A symbol such as ETH, or a CAIP-19 identifier. |
amount Required string, decimal | The amount received, in the asset’s whole units. |
settlement_id Required string | The deposit’s transaction hash, as CAIP-220 or a bare hash on the asset’s chain. |
settlement_address Required string | Your customer’s address the funds landed on. |
source_address string | The address the funds came from. |
originator_entity_did string, DID | The sending institution, when step 2 found one. |
beneficiary_entity_did string, DID | Your own DID, when no institution was found. Without one of these two DIDs, the request answers 400. |
beneficiary_id Required string | The id of your customer who received the funds. |
| Field | What to send |
|---|---|
ref (required), string, UUID | Your own reference for the transfer. Notabene never creates two transfers with the same ref, so a retry is safe. |
asset (required), string | A symbol such as ETH, or a CAIP-19 identifier. |
amount (required), string, decimal | The amount received, in the asset’s whole units. |
settlement_id (required), string | The deposit’s transaction hash, as CAIP-220 or a bare hash on the asset’s chain. |
settlement_address (required), string | Your customer’s address the funds landed on. |
source_address, string | The address the funds came from. |
originator_entity_did, string, DID | The sending institution, when step 2 found one. |
beneficiary_entity_did, string, DID | Your own DID, when no institution was found. Without one of these two DIDs, the request answers 400. |
beneficiary_id (required), string | The id of your customer who received the funds. |
4. If no institution sent it, name the sender
Record who your customer says sent the funds, and Notabene keeps the name on the transfer as IVMS-101. The call answers 204.
curl --request PATCH https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/parties/originator \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "party": { "id": "urn:uuid:92c6c2d2-fdad-44c2-8d20-25136a3c71f3", "name": "Ana Silva" }, "reason": "Declared by our customer" }'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/parties/originator', { method: 'PATCH', headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN', 'Content-Type': 'application/json', }, body: JSON.stringify({ "party": { "id": "urn:uuid:92c6c2d2-fdad-44c2-8d20-25136a3c71f3", "name": "Ana Silva" }, "reason": "Declared by our customer" }), }); const answer = response.status === 204 ? null : await response.json();import requests response = requests.patch( 'https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/parties/originator', headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}, json={ "party": { "id": "urn:uuid:92c6c2d2-fdad-44c2-8d20-25136a3c71f3", "name": "Ana Silva" }, "reason": "Declared by our customer" }, ) answer = None if response.status_code == 204 else response.json()
What to replace
8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80: The transferId from step 3urn:uuid:92c6c2d2-fdad-44c2-8d20-25136a3c71f3: An id of your choosing for the senderAna Silva: The name your customer declaredDeclared by our customer: Why you add the sender, kept on the transfer’s timeline
HTTP/1.1 204 No Contentcurl --request PATCH https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/parties/originator \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"party": {
"id": "urn:uuid:92c6c2d2-fdad-44c2-8d20-25136a3c71f3",
"name": "Ana Silva"
},
"reason": "Declared by our customer"
}'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/parties/originator', {
method: 'PATCH',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"party": {
"id": "urn:uuid:92c6c2d2-fdad-44c2-8d20-25136a3c71f3",
"name": "Ana Silva"
},
"reason": "Declared by our customer"
}),
});
const answer = response.status === 204 ? null : await response.json();import requests
response = requests.patch(
'https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/parties/originator',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
json={
"party": {
"id": "urn:uuid:92c6c2d2-fdad-44c2-8d20-25136a3c71f3",
"name": "Ana Silva"
},
"reason": "Declared by our customer"
},
)
answer = None if response.status_code == 204 else response.json()HTTP/1.1 204 No Content5. If your policies flag it, review the transfer
Your policies evaluate the transfer like any other. Creating it answers Active, and within a second the transfer reads SettledIncompleteAuthorization because the funds are already there. It stays that way until every institution on it has authorized it, and then becomes Settled. With no institution to wait for, that can happen as soon as your policies approve it.
When your policies flag the transfer, a person on your team approves or rejects it, in the dashboard under Inbox › Transactions, 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": "Declared sender reviewed" }'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": "Declared sender reviewed" }), }); 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": "Declared sender reviewed" }, ) 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": "Sender not accepted" }'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": "Sender not accepted" }), }); 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": "Sender not accepted" }, ) answer = None if response.status_code == 204 else response.json()
What to replace
8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80: The transferId from step 3Declared sender reviewed: Why your team approves it, kept on the transfer’s timelineSender not accepted: Why your team rejects it, kept on the transfer’s timeline
{
"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": "Declared sender reviewed"
}'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": "Declared sender reviewed"
}),
});
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": "Declared sender reviewed"
},
)
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": "Sender not accepted"
}'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": "Sender not accepted"
}),
});
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": "Sender not accepted"
},
)
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.
When you asked an institution for its data, the request only leaves once your side is approved. The institution then names its customer and sends the data, or rejects the request with a reason.
6. Credit your customer
Once every institution has authorized the transfer, its status changes to Settled and your webhook endpoint receives transfer.status_changed.v3. Events can repeat or arrive out of order, so handle them as described in Webhooks.
{
"eventType": "transfer.status_changed.v3",
"payload": {
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"for": "did:web:beneficiary.example:pt",
"fromStatus": "SettledIncompleteAuthorization",
"toStatus": "Settled",
"settlementId": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d"
}
}
{
"eventType": "transfer.status_changed.v3",
"payload": {
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"for": "did:web:beneficiary.example:pt",
"fromStatus": "SettledIncompleteAuthorization",
"toStatus": "Settled",
"settlementId": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d"
}
}If you would rather poll, read the transfer until its status is Settled, then credit your customer.
curl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80 \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80', { 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'}, ) answer = None if response.status_code == 204 else response.json()
What to replace
8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80: The transferId from step 3
{
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"status": "Settled",
"direction": "Incoming",
"settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
"requires_action": false
}curl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80 \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80', {
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'},
)
answer = None if response.status_code == 204 else response.json(){
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"status": "Settled",
"direction": "Incoming",
"settlement_id": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
"requires_action": false
}When a call fails
| Answer | What it means |
|---|---|
404 with address owner not found | No institution registered the source address. Ask your customer who sent the funds. |
400 with originator or originator_id is required | The request names neither the sending institution nor your own DID. |
400 with cannot force-authorize a concluded tx | The transfer is already Settled, so there is nothing to approve. |
| 401 | The token is missing, malformed or expired. Authentication explains how to get a new one. |
409 with a transfer with this ref already exists with different content | A transfer with this ref exists. Sending a create again says what it means. |
| Answer | What it means |
|---|---|
404 with address owner not found | No institution registered the source address. Ask your customer who sent the funds. |
400 with originator or originator_id is required | The request names neither the sending institution nor your own DID. |
400 with cannot force-authorize a concluded tx | The transfer is already Settled, so there is nothing to approve. |
401 | The token is missing, malformed or expired. Authentication explains how to get a new one. |
409 with a transfer with this ref already exists with different content | A transfer with this ref exists. Sending a create again says what it means. |
Common mistakes
| Mistake | What happens | Instead |
|---|---|---|
| Creating the transfer without looking for one first | Two transfers for the same deposit, when the sender had already created one. | Look for a transfer in step 1, and retry within your window. |
A new ref when you send a create again | A second transfer for the same funds. | Send the same request, with the same ref. |
| Mistake | What happens | Instead |
|---|---|---|
| Creating the transfer without looking for one first | Two transfers for the same deposit, when the sender had already created one. | Look for a transfer in step 1, and retry within your window. |
A new ref when you send a create again | A second transfer for the same funds. | Send the same request, with the same ref. |
How it works
On the counterparty’s side
Only when needed
Your wallet sees a deposit to one of your customers’ addresses, and your backend first checks that no transfer exists for it, retrying within a window you define. The call is in 1. Look for a transfer.
Ask Notabene which institution holds the address the funds came from, and if none is found, ask your customer who sent the funds. The call is in 2. Find out who sent it.
Create the transfer as the beneficiary, naming the sending institution, and Notabene then sends it a request for the sender’s data. The call is in 3. Create the transfer.
Create the transfer with your own DID as the beneficiary’s institution, then add the sender your customer told you about. The calls are in 3. Create the transfer and 4. If no institution sent it, name the sender.
- There is no counterparty institution to wait for.
Your policies evaluate the transfer like any other, and when they flag it, a person on your team reviews it. The calls are in 5. If your policies flag it, review the transfer.
- When you asked an institution for its data, the request only leaves once your side is approved.
When you asked an institution, it finds which of its customers sent the funds, names them on the transfer and sends you their data. If it did not send the funds, it rejects the request with a reason.
- Its data only leaves once its own policies have approved the request.
- If it does not answer, the requirement to receive its data is skipped after its time-out, and the transfer goes on without it.
Once every institution on the transfer has authorized it, the transaction status changes from SettledIncompleteAuthorization to Settled, and you can credit your customer.
Related
Updated about 3 hours ago
