Encryption
You choose who can read the personal data in a transfer on your side: Notabene, on your behalf, or only your own systems. That data is the IVMS-101 record of the originator and of the beneficiary, and it travels encrypted between institutions.
The options
PII escrow
Who can read the personal data
How PII escrow works is still to be published.placeholder
Hosted encryption
Who can read the personal data
Notabene, on your behalf. Personal data sent to you is encrypted to the PII key Notabene holds for you, and Notabene decrypts it when you read the transfer.
End-to-end encryption
Who can read the personal data
Only your systems and the counterparty’s. You encrypt what you send and decrypt what you receive, while Notabene passes the data on without decrypting it.
| Option | Who can read the personal data |
|---|---|
| PII escrow | How PII escrow works is still to be published. (placeholder) |
| Hosted encryption | Notabene, on your behalf. Personal data sent to you is encrypted to the PII key Notabene holds for you, and Notabene decrypts it when you read the transfer. |
| End-to-end encryption | Only your systems and the counterparty’s. You encrypt what you send and decrypt what you receive, while Notabene passes the data on without decrypting it. |
There is no setting for this in the dashboard, because the keys in your DID document decide it. Personal data sent to you is encrypted to your own PII key when your DID document has one, and to the key Notabene holds for you when it does not.
End-to-end encryption
Before you send personal data, you encrypt it to the counterparty’s PII key, but you do not sign it. Notabene adds the encryption and the signature of the message itself, and routes the message to the counterparty once your policies allow.
1. Find the counterparty’s key
Assess the transfer with include_pubkeys=true, and the response names the counterparty’s encryption key under beneficiaryVasp.encryptionKey, as a JSON Web Key. Without the flag, encryptionKey is null.
curl 'https://sandbox.notabene.id/api/v1/transfers/assess?include_pubkeys=true' \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "asset": "ETH", "amount": "0.5", "destinationAddress": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" }'const url = new URL('https://sandbox.notabene.id/api/v1/transfers/assess'); url.searchParams.set('include_pubkeys', 'true'); const response = await fetch(url, { method: 'POST', headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN', 'Content-Type': 'application/json', }, body: JSON.stringify({ "asset": "ETH", "amount": "0.5", "destinationAddress": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" }), }); const answer = response.status === 204 ? null : await response.json();import requests response = requests.post( 'https://sandbox.notabene.id/api/v1/transfers/assess', headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}, params={ "include_pubkeys": "true" }, json={ "asset": "ETH", "amount": "0.5", "destinationAddress": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" }, ) answer = None if response.status_code == 204 else response.json()
What to replace
eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed: The destination address
{
"travelRule": "Applies",
"beneficiaryVasp": {
"did": "did:web:beneficiary.example:pt",
"encryptionKey": {
"id": "did:web:beneficiary.example:pt#nb-pii",
"type": "JsonWebKey2020",
"controller": "did:web:beneficiary.example:pt",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "QPd12KIDz9nFplcr_zn0aUHZlS5xeAxF1yBhevpPixU",
"y": "eyqRi-hjnxxItYmZjWiHKvmJF3yMvCcz0T3lvVhZLXU"
}
}
}
}curl 'https://sandbox.notabene.id/api/v1/transfers/assess?include_pubkeys=true' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"asset": "ETH",
"amount": "0.5",
"destinationAddress": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
}'const url = new URL('https://sandbox.notabene.id/api/v1/transfers/assess');
url.searchParams.set('include_pubkeys', 'true');
const response = await fetch(url, {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"asset": "ETH",
"amount": "0.5",
"destinationAddress": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
}),
});
const answer = response.status === 204 ? null : await response.json();import requests
response = requests.post(
'https://sandbox.notabene.id/api/v1/transfers/assess',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
params={
"include_pubkeys": "true"
},
json={
"asset": "ETH",
"amount": "0.5",
"destinationAddress": "eip155:1:0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
},
)
answer = None if response.status_code == 204 else response.json(){
"travelRule": "Applies",
"beneficiaryVasp": {
"did": "did:web:beneficiary.example:pt",
"encryptionKey": {
"id": "did:web:beneficiary.example:pt#nb-pii",
"type": "JsonWebKey2020",
"controller": "did:web:beneficiary.example:pt",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "QPd12KIDz9nFplcr_zn0aUHZlS5xeAxF1yBhevpPixU",
"y": "eyqRi-hjnxxItYmZjWiHKvmJF3yMvCcz0T3lvVhZLXU"
}
}
}
}2. Send the data encrypted
Encrypt the originator’s whole IVMS-101 envelope, such as { "originator": { "originatorPerson": [ … ] } }, as one JWE compact string, and the beneficiary’s as another. Use ECDH-ES with A256GCM and no zip, and put the key’s id from step 1 in the JWE header as kid. Send each JWE as ivms_encrypted in place of ivms: the originator and the beneficiary each take one or the other, never both.
{
"originator_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
"originator": {
"ivms_encrypted": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..q4Ib0sbsWvd7Jp1V.Hk2YzN2RmM1N2ZkNTk4ZWQyZDU1Zjk4MjQ2NmJmZQ.n3dGZtYzA2ZDlkOGE0Yg"
},
"beneficiary_id": "urn:uuid:e7d14b39-8a2c-4f05-b6e1-2c9f7a3d5b18",
"beneficiary": {
"ivms_encrypted": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..Zr8kTn2wQe5mXa9L.Pq7vRw1xYz3aBc5dEf7gHi9jKl1mNo3pQr5sTu7vWx9yZ.c4Ve8tGk2Lq6Nx0Hb"
}
}
{
"originator_id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
"originator": {
"ivms_encrypted": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..q4Ib0sbsWvd7Jp1V.Hk2YzN2RmM1N2ZkNTk4ZWQyZDU1Zjk4MjQ2NmJmZQ.n3dGZtYzA2ZDlkOGE0Yg"
},
"beneficiary_id": "urn:uuid:e7d14b39-8a2c-4f05-b6e1-2c9f7a3d5b18",
"beneficiary": {
"ivms_encrypted": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..Zr8kTn2wQe5mXa9L.Pq7vRw1xYz3aBc5dEf7gHi9jKl1mNo3pQr5sTu7vWx9yZ.c4Ve8tGk2Lq6Nx0Hb"
}
}The rest of the call and its response are the same as in Withdrawals.
To add the data once the transfer exists, or for another recipient, send it for the transfer’s originator or beneficiary. Each payload is sealed to one recipient’s key, so you send it once per recipient.
curl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/parties/originator/encrypted-pii \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "ivms_encrypted": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..q4Ib0sbsWvd7Jp1V.Hk2YzN2RmM1N2ZkNTk4ZWQyZDU1Zjk4MjQ2NmJmZQ.n3dGZtYzA2ZDlkOGE0Yg" }'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/parties/originator/encrypted-pii', { method: 'POST', headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN', 'Content-Type': 'application/json', }, body: JSON.stringify({ "ivms_encrypted": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..q4Ib0sbsWvd7Jp1V.Hk2YzN2RmM1N2ZkNTk4ZWQyZDU1Zjk4MjQ2NmJmZQ.n3dGZtYzA2ZDlkOGE0Yg" }), }); 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/parties/originator/encrypted-pii', headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}, json={ "ivms_encrypted": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..q4Ib0sbsWvd7Jp1V.Hk2YzN2RmM1N2ZkNTk4ZWQyZDU1Zjk4MjQ2NmJmZQ.n3dGZtYzA2ZDlkOGE0Yg" }, ) answer = None if response.status_code == 204 else response.json()
What to replace
8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80: The transfer’s ideyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..q4Ib0sbsWvd7Jp1V.Hk2YzN2RmM1N2ZkNTk4ZWQyZDU1Zjk4MjQ2NmJmZQ.n3dGZtYzA2ZDlkOGE0Yg: The IVMS-101 record, encrypted by you to the counterparty’s key, as a JWE compact string
HTTP/1.1 204 No ContentHTTP/1.1 202 Acceptedcurl https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/parties/originator/encrypted-pii \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"ivms_encrypted": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..q4Ib0sbsWvd7Jp1V.Hk2YzN2RmM1N2ZkNTk4ZWQyZDU1Zjk4MjQ2NmJmZQ.n3dGZtYzA2ZDlkOGE0Yg"
}'const response = await fetch('https://sandbox.notabene.id/api/v1/transfers/8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80/parties/originator/encrypted-pii', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"ivms_encrypted": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..q4Ib0sbsWvd7Jp1V.Hk2YzN2RmM1N2ZkNTk4ZWQyZDU1Zjk4MjQ2NmJmZQ.n3dGZtYzA2ZDlkOGE0Yg"
}),
});
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/parties/originator/encrypted-pii',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
json={
"ivms_encrypted": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..q4Ib0sbsWvd7Jp1V.Hk2YzN2RmM1N2ZkNTk4ZWQyZDU1Zjk4MjQ2NmJmZQ.n3dGZtYzA2ZDlkOGE0Yg"
},
)
answer = None if response.status_code == 204 else response.json()HTTP/1.1 204 No ContentHTTP/1.1 202 AcceptedThe call answers 204 once the payload is disclosed to its recipient, or 202 while a compliance hold withholds it.
You can list the IVMS-101 fields inside the payload in ivms_encrypted_fields, though Notabene does not check the payload against them. The path format it accepts is still to be published. (placeholder)
3. Receive data encrypted to you
To receive personal data that only your systems can decrypt, add your own PII key to your DID document, among its key agreement keys. Host your DID document explains where that document lives.
The PII key Notabene holds for you has an id ending in #nb-pii, or #notabene-pii if your account moved from v2. Give your own key the id #pii, as a P-256 key, and personal data sent to you through Notabene is encrypted to it instead of Notabene’s.
In sandbox, Notabene publishes your DID document, so to test this there you send Notabene your document with your key added, as Host your DID document describes.
To get the data, read the transfer with include_ivms=true. Where the personal data is end-to-end encrypted, the originator and the beneficiary hold the JWE instead of the record, and you decrypt it with your private key.
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
{
"id": "8f3c2a1e-5b7d-4c90-a1f2-3e4d5c6b7a80",
"status": "Active",
"direction": "Incoming",
"originator": {
"id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
"ivms_encrypted": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..q4Ib0sbsWvd7Jp1V.Hk2YzN2RmM1N2ZkNTk4ZWQyZDU1Zjk4MjQ2NmJmZQ.n3dGZtYzA2ZDlkOGE0Yg"
},
"beneficiary": {
"id": "urn:uuid:e7d14b39-8a2c-4f05-b6e1-2c9f7a3d5b18",
"ivms_encrypted": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..Zr8kTn2wQe5mXa9L.Pq7vRw1xYz3aBc5dEf7gHi9jKl1mNo3pQr5sTu7vWx9yZ.c4Ve8tGk2Lq6Nx0Hb"
}
}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",
"originator": {
"id": "urn:uuid:3c9a7e52-41f6-4b8d-a0e2-6d1f9b4c8a27",
"ivms_encrypted": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..q4Ib0sbsWvd7Jp1V.Hk2YzN2RmM1N2ZkNTk4ZWQyZDU1Zjk4MjQ2NmJmZQ.n3dGZtYzA2ZDlkOGE0Yg"
},
"beneficiary": {
"id": "urn:uuid:e7d14b39-8a2c-4f05-b6e1-2c9f7a3d5b18",
"ivms_encrypted": "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..Zr8kTn2wQe5mXa9L.Pq7vRw1xYz3aBc5dEf7gHi9jKl1mNo3pQr5sTu7vWx9yZ.c4Ve8tGk2Lq6Nx0Hb"
}
}Customers
You can also store a customer’s profile encrypted, as profile.ivms_encrypted, which Customers explains.
Moving from v2
If you encrypt personal data yourself on v2, field by field, keep doing so until you move and add the originator’s and the beneficiary’s JWEs next to the encrypted fields, as Moving from v2 to v2.1 explains. On v2.1 you send only the JWEs.
Updated 4 days ago
