Webhooks
Notabene sends webhooks to tell your backend when something happens on a transfer, so you do not have to poll for changes. You set up your endpoint once in the dashboard, and from then on your backend receives five events. Some keep your records in sync, and others tell you that Notabene is waiting for something from you.
Add your webhook endpoint
In the dashboard, go to Settings › Webhooks, add the HTTPS address of your endpoint and choose the events it should receive. Subscribe to transfer.created.v3, transfer.status_changed.v3, transfer.agent_status_changed.v3, requirements.pending.v3 and requirements.authorization_requested.v3.
Each endpoint has its own signing secret, which starts with whsec_. You can reveal or rotate it on the endpoint’s page, and the previous secret keeps working for a while after a rotation.
Verify each delivery
Notabene delivers webhooks through Svix, and every delivery is signed. Check the signature with a Svix library before you use the content, then answer with a 2xx status.
const { Webhook } = await import("svix");
const wh = new Webhook(process.env.NOTABENE_WEBHOOK_SECRET);
// throws when the signature is not valid
const event = wh.verify(rawBody, {
"svix-id": headers["svix-id"],
"svix-timestamp": headers["svix-timestamp"],
"svix-signature": headers["svix-signature"],
});
const { Webhook } = await import("svix");
const wh = new Webhook(process.env.NOTABENE_WEBHOOK_SECRET);
// throws when the signature is not valid
const event = wh.verify(rawBody, {
"svix-id": headers["svix-id"],
"svix-timestamp": headers["svix-timestamp"],
"svix-signature": headers["svix-signature"],
});Verify the raw request body exactly as it arrived, before any JSON parsing.
The five events
With these five events, your backend knows when a transfer arrives, when it needs to act and when a transfer is ready to settle, without polling.
| Event | Sent to | When | What to do |
|---|---|---|---|
transfer.created.v3 | Both sides | A transfer exists on your node, either one you created or a deposit that the originator has approved. | Record it. Use direction to tell withdrawals from deposits. |
transfer.status_changed.v3 | Both sides | The transaction status changes. | Keep your copy of the transfer in step, for example at Settled. |
transfer.agent_status_changed.v3 | Both sides | An institution on the transfer authorizes it, yours or the counterparty. | Settle a withdrawal once every institution is Authorized. |
requirements.pending.v3 | Both sides | Requirements are still open on the transfer. | Resolve the ones that are yours, for example confirming a deposit address you did not register in advance. |
requirements.authorization_requested.v3 | To be confirmedplaceholder | The transfer waits for your authorization. | Authorize or reject the transfer. |
| Event | Sent to | When | What to do |
|---|---|---|---|
transfer.created.v3 | Both sides | A transfer exists on your node, either one you created or a deposit that the originator has approved. | Record it. Use direction to tell withdrawals from deposits. |
transfer.status_changed.v3 | Both sides | The transaction status changes. | Keep your copy of the transfer in step, for example at Settled. |
transfer.agent_status_changed.v3 | Both sides | An institution on the transfer authorizes it, yours or the counterparty. | Settle a withdrawal once every institution is Authorized. |
requirements.pending.v3 | Both sides | Requirements are still open on the transfer. | Resolve the ones that are yours, for example confirming a deposit address you did not register in advance. |
requirements.authorization_requested.v3 | To be confirmed (placeholder) | The transfer waits for your authorization. | Authorize or reject the transfer. |
The dashboard also lists other event types, which exist for Payments and for compatibility with earlier versions of Notabene.
A transfer is created
Notabene sends transfer.created.v3 when a transfer exists on your node, either one you created or a deposit arriving from a counterparty institution. Use direction to tell them apart, and ref to match your own transfers with your records.
{
"eventType": "transfer.created.v3",
"payload": {
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"for": "did:web:originator.example:pt",
"from": "did:web:originator.example:pt",
"direction": "Outgoing",
"counterparty": "did:web:beneficiary.example:pt",
"asset": "eip155:1/slip44:60",
"amount": "0.01",
"ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40"
}
}
{
"eventType": "transfer.created.v3",
"payload": {
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"for": "did:web:originator.example:pt",
"from": "did:web:originator.example:pt",
"direction": "Outgoing",
"counterparty": "did:web:beneficiary.example:pt",
"asset": "eip155:1/slip44:60",
"amount": "0.01",
"ref": "0b6a4f8e-2d1c-4e7a-9b3f-5c8d2e1a7f40"
}
}A deposit only arrives once the originator’s side has approved it.
The status changes
Notabene sends transfer.status_changed.v3 when the transaction status changes, with the previous status in fromStatus and the new one in toStatus. The transaction status combines the status of every institution on the transfer with the settlement.
- If
toStatusisSettled, the settlement was recorded, and the event includessettlementId. - If
toStatusisRejectedorCanceled, the transfer stops there. - The event does not say whether your policies flagged the transfer. The transfer reads
requires_action: truewhile it waits for your team.
{
"eventType": "transfer.status_changed.v3",
"payload": {
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"for": "did:web:originator.example:pt",
"fromStatus": "ReadyToSettle",
"toStatus": "Settled",
"settlementId": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d"
}
}
{
"eventType": "transfer.status_changed.v3",
"payload": {
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"for": "did:web:originator.example:pt",
"fromStatus": "ReadyToSettle",
"toStatus": "Settled",
"settlementId": "eip155:1:tx/0x7d3c9e1f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d"
}
}Your own status, shown as My Status in the dashboard, is a separate value: read it as the status of your entry in the transfer’s agents. The event does not carry the reason for a rejection, which is in your audit trail. Statuses lists every status and what to do at each.
An institution authorizes
Notabene sends transfer.agent_status_changed.v3 when the status of an institution on the transfer changes, yours or the counterparty’s, with that institution in agentId. This is how you learn that a transfer is ready to settle: once every institution is Authorized, the originator’s institution sends the funds. No other event marks that moment.
{
"eventType": "transfer.agent_status_changed.v3",
"payload": {
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"for": "did:web:originator.example:pt",
"agentId": "did:web:beneficiary.example:pt",
"fromStatus": "Pending",
"toStatus": "Authorized"
}
}
{
"eventType": "transfer.agent_status_changed.v3",
"payload": {
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"for": "did:web:originator.example:pt",
"agentId": "did:web:beneficiary.example:pt",
"fromStatus": "Pending",
"toStatus": "Authorized"
}
}Here the beneficiary’s institution has authorized a withdrawal you sent, and yours already was, so you can send the funds.
Requirements are waiting
Notabene sends requirements.pending.v3 with the requirements that are still open on the transfer. Look at the ones whose agentId is your DID: agentId is the institution that must meet a requirement, and from the one that set it. Notabene handles some of them by itself, and the others need an answer from you.
- If Notabene cannot match the deposit address to you, because you did not register it or confirm it before,
WalletAddressConfirmationwaits for your answer. Register the address with your customer, which confirms it, or reject it if it is not yours. - Once the address is confirmed, the Travel Rule data arrives and
BeneficiaryNameMatchfollows. Compare the name with your records. - If your setup includes an internal review, you also receive
InternalComplianceCheck.
In this example you receive the transfer, and you answer the address confirmation by registering the address with your customer.
{
"eventType": "requirements.pending.v3",
"payload": {
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"for": "did:web:beneficiary.example:pt",
"requirements": [
{
"agentId": "did:web:beneficiary.example:pt",
"from": "did:web:originator.example:pt",
"id": "req-1",
"status": "Pending",
"type": "WalletAddressConfirmation"
},
{
"agentId": "did:web:originator.example:pt",
"from": "did:web:beneficiary.example:pt",
"id": "req-2",
"status": "Pending",
"type": "ReceiveTRInfo"
}
]
}
}
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"
}
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 address
{
"eventType": "requirements.pending.v3",
"payload": {
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"for": "did:web:beneficiary.example:pt",
"requirements": [
{
"agentId": "did:web:beneficiary.example:pt",
"from": "did:web:originator.example:pt",
"id": "req-1",
"status": "Pending",
"type": "WalletAddressConfirmation"
},
{
"agentId": "did:web:originator.example:pt",
"from": "did:web:beneficiary.example:pt",
"id": "req-2",
"status": "Pending",
"type": "ReceiveTRInfo"
}
]
}
}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"
}Only the first requirement is yours, while the second asks the counterparty to send its Travel Rule data. Requirements you resolve lists every requirement you resolve and its answers.
Your authorization is requested
Notabene sends requirements.authorization_requested.v3 when the transfer is waiting for your authorization, and you either authorize it or reject it with a reason. authorize accepts a transfer that asks for your authorization, while a transfer your policies flagged is approved with force-authorize, as described in Withdrawals.
curl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/authorize \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --header 'Content-Type: application/json' \ --data '{}'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/authorize', { method: 'POST', headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN', 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); 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/authorize', headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}, json={}, ) 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": "Destination not allowed" }'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": "Destination not allowed" }), }); 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": "Destination not allowed" }, ) answer = None if response.status_code == 204 else response.json()
What to replace
8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80: The transfer’s id, from the eventDestination not allowed: Why you reject the transfer
{
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"type": "Transfer",
"status": "Active",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt",
"agents": [
{
"agent_did": "did:web:beneficiary.example:pt",
"role": "VASP",
"status": "Pending"
}
]
}{
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"type": "Transfer",
"status": "Rejected",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt",
"agents": [
{
"agent_did": "did:web:beneficiary.example:pt",
"role": "VASP",
"status": "Rejected"
}
]
}curl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/authorize \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{}'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/authorize', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
});
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/authorize',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
json={},
)
answer = None if response.status_code == 204 else response.json(){
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"type": "Transfer",
"status": "Active",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt",
"agents": [
{
"agent_did": "did:web:beneficiary.example:pt",
"role": "VASP",
"status": "Pending"
}
]
}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": "Destination not allowed"
}'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": "Destination not allowed"
}),
});
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": "Destination not allowed"
},
)
answer = None if response.status_code == 204 else response.json(){
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"type": "Transfer",
"status": "Rejected",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt",
"agents": [
{
"agent_did": "did:web:beneficiary.example:pt",
"role": "VASP",
"status": "Rejected"
}
]
}Both calls answer 200 with the transfer, and your side stays Pending after you authorize while one of your requirements is still open. When exactly this event is sent is still to be confirmed. (placeholder)
Requirements you resolve
A requirements.pending.v3 event lists every open requirement, but only those whose agentId is your DID concern you, and only some of those need a call. You resolve each of them with the same call, POST /api/v1/transfers/{id}/requirements/{requirement}/resolve, where {requirement} is the name in the second column and the body holds your outcome.
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 eventbeneficiary-name-match: The name in the second column of the table belowmatched: One of the outcomes in the table below
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| Type | In the path | What it asks | Outcome |
|---|---|---|---|
WalletAddressConfirmation | wallet-address-confirmation | Confirm that the deposit address belongs to your customer. You are not asked again for an address you confirmed before. | confirmed or rejected, as below. |
BeneficiaryNameMatch | beneficiary-name-match | Compare the beneficiary’s name with your records. It follows the address confirmation, once the Travel Rule data has arrived. | matched, partial_match (holds the transfer for review) or mismatch. |
InternalComplianceCheck | internal-compliance-check | Complete your own compliance review. | completed or rejected. |
TransmitTRData, ReceiveTRInfo, WalletConfirmationForDisclosure | Send or receive the Travel Rule data, or wait for the counterparty’s confirmation. | Nothing to send. Notabene and the counterparty handle these. |
| Type | In the path | What it asks | Outcome |
|---|---|---|---|
WalletAddressConfirmation | wallet-address-confirmation | Confirm that the deposit address belongs to your customer. You are not asked again for an address you confirmed before. | confirmed or rejected, as below. |
BeneficiaryNameMatch | beneficiary-name-match | Compare the beneficiary’s name with your records. It follows the address confirmation, once the Travel Rule data has arrived. | matched, partial_match (holds the transfer for review) or mismatch. |
InternalComplianceCheck | internal-compliance-check | Complete your own compliance review. | completed or rejected. |
TransmitTRData, ReceiveTRInfo, WalletConfirmationForDisclosure | Send or receive the Travel Rule data, or wait for the counterparty’s confirmation. | Nothing to send. Notabene and the counterparty handle these. |
You rarely answer the address confirmation yourself. Notabene satisfies it by itself when you registered the address or confirmed it before, and registering the address now with POST /api/v1/wallets satisfies it too.
To confirm the address for this transfer only, without registering it, answer confirmed. When the address is not yours, answer rejected, which rejects the transfer with the reason code AC03, and the counterparty then stops treating the address as yours.
Read the transfer
Webhooks carry identifiers and statuses but never personal data. When you need the full picture, for example the originator and the beneficiary, or the requirements that are still open, read the transfer.
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 transfer’s id, from the event
{
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"type": "Transfer",
"status": "Active",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt",
"asset": "eip155:1/slip44:60",
"amount": "0.5",
"requires_action": true,
"agents": [
{
"agent_did": "did:web:originator.example:pt",
"role": "VASP",
"status": "Authorized",
"policies": [
{
"type": "WalletAddressConfirmation",
"status": "Satisfied",
"from_did": "did:web:beneficiary.example:pt"
}
]
},
{
"agent_did": "did:web:beneficiary.example:pt",
"role": "VASP",
"status": "Pending",
"policies": [
{
"type": "ReceiveTRInfo",
"status": "Pending",
"from_did": "did:web:originator.example:pt"
},
{
"type": "Authorization",
"status": "Satisfied",
"from_did": "did:web:originator.example:pt"
},
{
"type": "BeneficiaryNameMatch",
"status": "Pending",
"from_did": "did:web:beneficiary.example:pt"
}
]
}
]
}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",
"type": "Transfer",
"status": "Active",
"direction": "Incoming",
"counterparty": "did:web:originator.example:pt",
"asset": "eip155:1/slip44:60",
"amount": "0.5",
"requires_action": true,
"agents": [
{
"agent_did": "did:web:originator.example:pt",
"role": "VASP",
"status": "Authorized",
"policies": [
{
"type": "WalletAddressConfirmation",
"status": "Satisfied",
"from_did": "did:web:beneficiary.example:pt"
}
]
},
{
"agent_did": "did:web:beneficiary.example:pt",
"role": "VASP",
"status": "Pending",
"policies": [
{
"type": "ReceiveTRInfo",
"status": "Pending",
"from_did": "did:web:originator.example:pt"
},
{
"type": "Authorization",
"status": "Satisfied",
"from_did": "did:web:originator.example:pt"
},
{
"type": "BeneficiaryNameMatch",
"status": "Pending",
"from_did": "did:web:beneficiary.example:pt"
}
]
}
]
}Add include_ivms=true to include the originator’s and the beneficiary’s personal data.
The envelope and delivery
Every delivery has the same envelope as the events above: the event type and a payload. The payload always includes the transfer id, and for, the DID of your institution that the delivery is for. Its fields are written in camelCase, such as settlementId and toStatus, where the API writes settlement_id.
Deliveries are signed by Svix and retried until your endpoint answers with a 2xx status. The same delivery can arrive more than once with the same svix-id header, so ignore repeats. Deliveries are not guaranteed to arrive in order, so rely on the status in the event, or read the transfer, rather than on the order in which events arrive.
How it works
In the dashboard, under Settings › Webhooks: Add your webhook endpoint.
Check the Svix signature before anything else: Verify each delivery.
A transfer exists on your node, yours or an incoming one: A transfer is created.
The transaction status changed: The status changes.
An institution authorized the transfer, and once every one has, a withdrawal is ready to settle: An institution authorizes.
Requirements are open, and some may be yours: Requirements are waiting.
The transfer waits for your authorization: Your authorization is requested.
Events carry no personal data, so read the transfer for the details: Read the transfer.
Updated about 3 hours ago
