# Send assets with PII

This workflow covers outgoing transfers where you must submit PII (Personally Identifiable Information). You create the Travel Rule message, collect PII from your end user, submit it through the API, and then create the transfer intent with the `suggestedIntentId` the message returns.

Creating the message first is the only supported entry path. Ripple Custody doesn't create a Notabene transfer from a transfer intent on its own.

## Prerequisites

- [Travel Rule setup](/pt-br/products/custody/v1.40/compliance/travel-rule/setup) complete.
- Wallet addresses registered with Notabene. Ripple Custody registers them automatically when you connect your Notabene account and when you create new wallets.


## Process flow

```mermaid
sequenceDiagram
    participant Customer
    participant EndUser as End User
    participant Custody
    participant Notabene
    participant Blockchain

    Customer->>Custody: 1. Create Travel Rule message
    Custody->>Notabene: Forward to Notabene
    Notabene-->>Custody: Return transfer details
    Custody-->>Customer: Return suggestedIntentId, complianceTravelRuleId,<br/>transfer details (including travelRulePolicyId)

    Customer->>Custody: 2. Get transfer status (check PII requirements)
    Custody-->>Customer: Return isTravelRule, presentationDefinitionUrl

    Customer->>EndUser: 3. Request PII
    EndUser-->>Customer: Provide PII

    Customer->>Custody: 4. Submit PII via API
    Custody->>Notabene: Forward PII
    Notabene-->>Custody: PII attached
    Custody-->>Customer: Confirmed

    Customer->>Custody: 5. Create Transfer Order Intent (using suggestedIntentId)

    Custody->>Custody: 6. Screen risk (if configured)
    alt Screening failed
        Custody->>Custody: AUTO_REJECTED (no Travel Rule check)
    else Screening passed or not configured
        Custody->>Notabene: 7. Travel Rule check
    end

    Custody->>Custody: 8. Compliance decision

    alt AUTO_APPROVED
        Custody->>Blockchain: 9. Execute transaction
        Custody->>Notabene: Notify settlement
    else AUTO_REJECTED
        Custody->>Notabene: Notify rejection
    end
```

## Step 1: Create Travel Rule message

Create a Travel Rule message with counterparty information:

```bash
POST /v1/domains/{domainId}/compliance/travel-rule/providers/NOTABENE/messages
```

Notabene is the only supported Travel Rule provider, so the `provider` path segment is always `NOTABENE`.

**Request body**:

| Field | Type | Description |
|  --- | --- | --- |
| `originator` | object | Originator identifier (contains `@id` - DID or identifier for the party) |
| `beneficiary` | object | Beneficiary identifier (contains `@id` - DID or identifier for the party) |
| `asset` | string | Asset identifier (e.g., `bip122:000000000019d6689c085ae165831e93/slip44:0`). See [Notabene asset registry](https://devx.notabene.id/docs/supported-assets) for supported formats. |
| `amount` | string | Transfer amount |
| `ref` | string | Reference identifier for the transfer |
| `agents` | array | Agents involved in the transfer. Each agent has `@id`, `for`, and `role` (values: `VASP`, `Custodian`, `SettlementAddress`, `SourceAddress`, `Gateway`, `Unknown`) |


**Response**:

| Field | Type | Description |
|  --- | --- | --- |
| `createTransfer201Response.transfer` | object | Transfer details from Notabene. If the beneficiary VASP attached a Notabene policy to the transfer, the details include that policy's ID. Use it as `{travelRulePolicyId}` in [Step 4, Option C](#option-c-present-encrypted-pii-for-a-specific-policy-stored-on-receiver-side-only). |
| `suggestedIntentId` | string (uuid) | The intent ID to use when creating the transfer intent |
| `complianceTravelRuleId` | string (uuid) | The ID of this Travel Rule record in Ripple Custody. Use it as `{travelRuleId}` in the status and PII endpoints. |


## Step 2: Check PII requirements

Get the transfer to check whether Notabene requires PII and what the counterparty requests:

```bash
GET /v1/domains/{domainId}/compliance/travel-rule/providers/NOTABENE/messages/{travelRuleId}
```

Use the `complianceTravelRuleId` from Step 1 as `{travelRuleId}`.

**Key response fields**:

| Field | Type | Description |
|  --- | --- | --- |
| `transfer.isTravelRule` | boolean | Whether the transfer requires Travel Rule compliance. |
| `transfer.presentationDefinitionUrl` | string | URL of the presentation definition that lists the PII the counterparty requires. |
| `transfer.status` | string | Notabene transfer status. |


Fetch the document at `presentationDefinitionUrl`. A presentation definition is a JSON document, in the Presentation Exchange format, that lists the IVMS-101 fields the beneficiary VASP's jurisdiction requires. Notabene publishes one per jurisdiction.

## Step 3: Collect PII from end user

Collect required PII from your end user according to the IVMS-101 standard and the requirements in the presentation definition from Step 2.

## Step 4: Submit PII via API

Notabene offers two ways to attach PII to a transfer, and Ripple Custody exposes both:

- **Append** (Option A). You submit plaintext IVMS-101 data. Notabene encrypts it with keys that Notabene manages and stores the encrypted PII on both your Notabene entity and the beneficiary's. Notabene can decrypt it, which lets Notabene run checks such as name screening on it.
- **Present** (Options B and C). You encrypt the IVMS-101 data yourself before you submit it. Notabene can't read the payload and forwards it to the beneficiary VASP. Only the beneficiary side stores the PII. Your Notabene entity and the Notabene platform don't store it.


In all cases, Ripple Custody forwards the PII to Notabene without storing it.

#### How end-to-end encryption works

For Options B and C, you produce the encrypted payload. Notabene's end-to-end encryption uses ECDH-ES (RFC 7518) key agreement on the P-256 curve with the beneficiary VASP's public key, which the beneficiary publishes in its DIDdoc, and AES-256-GCM for content encryption. The encrypted payload is a compact JWE string. You publish your own public key in your DIDdoc so that counterparties can encrypt PII for you in the same way. For details and reference implementations, see Notabene's [PII encryption guide](https://devx.notabene.id/docs/pii-encryption-guide) and [Encryption managed by the customer](https://devx.notabene.id/docs/self-encryption).

### Option A: Append PII (stored on both sides)

Use this option when both sender and receiver need access to the PII. You submit the PII in plaintext IVMS-101 format and Notabene encrypts it.

```bash
POST /v1/domains/{domainId}/compliance/travel-rule/providers/NOTABENE/messages/{travelRuleId}/pii
```

**Request body**:

| Field | Type | Required | Description |
|  --- | --- | --- | --- |
| `ivms101` | object | Yes | IVMS-101 formatted PII data |
| `originator` | object | No | Originator details |
| `beneficiary` | object | No | Beneficiary details |


**Response:** `200 OK`

### Option B: Present encrypted PII (stored on receiver side only)

Use this option for end-to-end encrypted PII that Notabene can't read.

```bash
POST /v1/domains/{domainId}/compliance/travel-rule/providers/NOTABENE/messages/{travelRuleId}/encrypted-pii
```

**Query parameters**:

| Parameter | Type | Default | Description |
|  --- | --- | --- | --- |
| `skipValidation` | boolean | `false` | Corresponds to Notabene's `skipValidation` option. When `true`, Notabene skips validation of the PII against the jurisdiction's requirements, which allows an incomplete submission. |


**Request body**:

| Field | Type | Required | Description |
|  --- | --- | --- | --- |
| `ivms101` | object | Yes | End-to-end encrypted IVMS-101 data |


**Response:** `202 Accepted` (asynchronous processing)

### Option C: Present encrypted PII for a specific policy (stored on receiver side only)

The policies in this endpoint are Notabene authorization requirements, not Ripple Custody governance policies. The beneficiary VASP defines them in Notabene to state what it needs before it authorizes an incoming transfer, for example a Travel Rule message with originator PII. Each policy references the presentation definition that satisfies it.

Use this option to fulfill one specific policy that the beneficiary VASP attached to the transfer. `travelRulePolicyId` is the ID of that Notabene policy. Notabene returns it in the transfer details in the response to [Step 1](#step-1-create-travel-rule-message), so you don't need to call Notabene directly to get it.

```bash
POST /v1/domains/{domainId}/compliance/travel-rule/providers/NOTABENE/messages/{travelRuleId}/policies/{travelRulePolicyId}/encrypted-pii
```

**Query parameters and request body:** Same as Option B.

**Response:** `202 Accepted` (asynchronous processing)

Options B and C carry PII that you encrypted with the beneficiary VASP's public key. Notabene can't read the payload and doesn't store it on your Notabene entity or on the Notabene platform. Only the beneficiary side holds it.

## Step 5: Create Transfer Order intent

Create the Transfer Order intent using the `suggestedIntentId` returned in Step 1.

**Transfer Order intent payload**:

| Field | Type | Required | Description |
|  --- | --- | --- | --- |
| `payload.accountId` | string (uuid) | Yes | Source account ID |
| `payload.ledgerId` | string | No | Ledger identifier |
| `payload.parameters.operation` | object | Yes | Transfer operation details including `destination`, `amount`, and `type` |
| `payload.type` | string | Yes | Must be `v0_CreateTransferOrder` |


Critical
Use the `suggestedIntentId` from Step 1 when creating the intent. This links the compliance check to your transaction.

## Step 6: Risk screening

If transaction screening is configured (Chainalysis or Elliptic), Ripple Custody screens the transaction first.

- **If screening fails** (high risk score): The transaction is immediately rejected. No Travel Rule check occurs.
- **If screening passes**: The workflow proceeds to the Travel Rule check.


## Step 7: Travel Rule check

Notabene verifies counterparty information and PII.

## Step 8: Compliance decision

Ripple Custody evaluates the results:

| Screening Result | Travel Rule Result | Decision |
|  --- | --- | --- |
| Approved | Approved | `AUTO_APPROVED` |
| Rejected | — | `AUTO_REJECTED` (Travel Rule skipped) |
| Approved | Rejected | `AUTO_REJECTED` |
| Inconclusive | Inconclusive | `NEED_EXPLICIT_DECISION` |


If the decision is `NEED_EXPLICIT_DECISION`, manual review is required in Ripple Custody.

If only one check is configured (screening only or Travel Rule only), the decision is based on that single check.

## Step 9: Execution and settlement

- If `AUTO_APPROVED`: Transaction executes on blockchain, then Notabene is notified of settlement.
- If `AUTO_REJECTED`: Intent closes, then Notabene is notified of rejection.
- If the intent expires before execution (30 days by default): Ripple Custody automatically rejects the Travel Rule transfer on Notabene.


## Verify transfer status

Poll the Travel Rule transfer status to confirm successful execution:

```bash
GET /v1/domains/{domainId}/compliance/travel-rule/providers/NOTABENE/messages/{travelRuleId}
```

**Key response fields**:

| Field | Type | Description |
|  --- | --- | --- |
| `transfer.status` | string | Notabene transfer status (e.g., `SETTLED`, `REJECTED`) |
| `transfer.direction` | string | `OUTGOING` for this workflow |


## Next steps

- [Send without PII](/pt-br/products/custody/v1.40/compliance/travel-rule/outgoing-no-pii) — When PII is not required
- [Receive assets](/pt-br/products/custody/v1.40/compliance/travel-rule/incoming) — Handle incoming Travel Rule transfers