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

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

You choose end-to-end encryption in the dashboard. Where you choose it in the dashboard is still to be published. (placeholder)

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"
      }
    }
  }
}

Notabene will also name the keys to encrypt to in its webhooks. Which webhooks name the keys, and how, is still to be published. (placeholder)

2. Send the data encrypted

Encrypt the whole IVMS-101 record of the originator, and that of the beneficiary, as one JWE compact string each. Send each one 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 published in each DID document has an id ending in #nb-pii, like the key you found in step 1. The id your own key should use is still to be published. (placeholder)

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, you also encrypt the whole payload as one JWE on v2.1, as Moving from v2 to v2.1 says.


Did this page help you?