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.

OptionWho can read the personal data
PII escrowHow PII escrow works is still to be published. (placeholder)
Hosted encryptionNotabene, 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 encryptionOnly 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.

API reference
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
Response200
{
  "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.

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

API reference
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 id
  • eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImRpZDp3ZWI6YmVuZWZpY2lhcnkuZXhhbXBsZTpwdCNuYi1waWkifQ..q4Ib0sbsWvd7Jp1V.Hk2YzN2RmM1N2ZkNTk4ZWQyZDU1Zjk4MjQ2NmJmZQ.n3dGZtYzA2ZDlkOGE0Yg: The IVMS-101 record, encrypted by you to the counterparty’s key, as a JWE compact string
Disclosed204
HTTP/1.1 204 No Content
Held202
HTTP/1.1 202 Accepted
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()
HTTP/1.1 204 No Content
HTTP/1.1 202 Accepted

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

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


Did this page help you?