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.

Automatic
Manual review
Steps
Automatic
Manual review

Done by Notabene

On the counterparty’s side

Only when needed

← → the step before and after · Esc all steps
Step 1
1A transfer arrives for your customer
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.

← → the step before and after · Esc all steps
Step
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.

Good to know
  • 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.
← → the step before and after · Esc all steps
Only when needed
Register the deposit address
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.

← → the step before and after · Esc all steps
Only when needed
Report the name check
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.

← → the step before and after · Esc all steps
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.

← → the step before and after · Esc all steps
Manual review · Step 4
4Your 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.

Good to know
  • Rejecting tells the sending institution, with your reason.
← → the step before and after · Esc all steps
Step
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.

Good to know
  • You do not record the settlement yourself: the sender does.
← → the step before and after · Esc all steps
Step 5
5Match the deposit to its transfer
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.

← → the step before and after · Esc all steps
Step
Credit your customer
Your backend

Once the lookup returns the transfer as Settled, credit your customer.

← → the step before and after · Esc all steps
  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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.

JSON
{
  "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.

API reference
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
Response200
{
  "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-10
  • urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27: The id of your customer who owns the address
  • 8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80: The transfer’s id, from the webhook
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-20T09:14:03Z"
}
Response204
HTTP/1.1 204 No Content
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()
{
  "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 Content

Rejecting 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.

API reference
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 webhook
  • matched: matched, partial_match or mismatch
Response204
HTTP/1.1 204 No Content
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()
HTTP/1.1 204 No Content
OutcomeWhat happens
matchedThe check is satisfied, and your side goes on to your policies.
partial_matchThe transfer is held for a person on your team to review.
mismatchYour side rejects the transfer.
OutcomeWhat happens
matchedThe check is satisfied, and your side goes on to your policies.
partial_matchThe transfer is held for a person on your team to review.
mismatchYour 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 webhook
  • Reviewed by compliance: Why your team approves it, kept on the transfer’s timeline
  • Beneficiary account closed: Why your team rejects it, shared with the sender
Reject response200
{
  "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.

API reference
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 chain
  • eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed: Your customer’s address the funds landed on
Nothing yet200
{
  "data": [],
  "all_filters_applied": true,
  "pagination": {
    "total": 0,
    "limit": 25
  }
}
Found200
{
  "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

AnswerWhat it means
400 with expected required property reason to be presentApproving needs a reason.
400 with cannot force-authorize a concluded txThe transfer is already Settled, canceled or reverted, so there is nothing to approve.
401The token is missing, malformed or expired. Authentication explains how to get a new one.
409 on approveThe transfer’s compliance inputs changed while it was being approved. Send the request again.
AnswerWhat it means
400 with expected required property reason to be presentApproving needs a reason.
400 with cannot force-authorize a concluded txThe transfer is already Settled, canceled or reverted, so there is nothing to approve.
401The token is missing, malformed or expired. Authentication explains how to get a new one.
409 on approveThe transfer’s compliance inputs changed while it was being approved. Send the request again.

Common mistakes

MistakeWhat happensInstead
Reading the sender’s data before the address is confirmedThe 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=trueThe 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 transferThe 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.
MistakeWhat happensInstead
Reading the sender’s data before the address is confirmedThe 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=trueThe 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 transferThe 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

  • Transfers: what each institution sees on a transfer, and why statuses differ.
  • Statuses: every status and what your backend does at each.
  • Webhooks: every event and every requirement you may have to answer.
  • Errors: every error message and what to do.

Did this page help you?