# Create and manage identities Identity Management v3 This topic describes the v3 identity model. **Identity Management v3** is recommended for new integrations. Identity Management v3 is an **upcoming feature**. For information on availability, early access, or migration, please contact your Ripple representative. The supported payment corridors will be: - US • USD - EU • EUR - MX • MXN - BR • BRL - CO • COP - CA • CAD - GB • GBP - Africa • GHS, NGN, RWF, UGX, ZAR, ZMW This tutorial shows how to use the **Identity Management v3** identity endpoints to: - Create a new identity - Retrieve an identity by ID (and by version) - List identities with filters and pagination - Update identity details - Deactivate an identity It builds on the model described in [Payment identities](/products/payments-direct-2/api-docs/concepts/payment-identities) and focuses on the v3 identity schema. internalId A key behavior in v3 is that **at most one ACTIVE identity** in your environment can use a given `internalId`. Attempts to create or update an identity so that its `internalId` collides with another ACTIVE identity result in **409 Conflict**. ## Before you begin To follow these examples, you need: - Base URL for the Identity Management (for example, `https://{base-url}`) - An access token with the following scopes (or equivalent): - `participants:create `for creating identities - `participants:read` for reading and listing identities - `participants:update` for updating and deactivating identities - The v3 identity model from **Payment identities** (INDIVIDUAL vs BUSINESS and ORIGINATOR vs BENEFICIARY) In all examples below, replace: - `{base-url}` with your actual PII base URL - `` with a valid OAuth2 access token - `{identity-id} `with a real `identityId` returned by the API ## Create an identity Use `POST /v3/identities` to create a new identity. At minimum, you must provide: - `identityType` – INDIVIDUAL or BUSINESS - `paymentRole` – ORIGINATOR or BENEFICIARY - Either individual or business details, depending on `identityType` - `internalId` for ORIGINATOR identities (required by corridor rules) For **BENEFICIARY** identities, `internalId` is optional, but recommended if you want to link beneficiaries to your own customer records. Deduplication via internalId Identity Management v3 enforces that at most one **ACTIVE** identity per organization can use a given `internalId`. - If you try to create an identity and the provided `internalId` is already used by another **ACTIVE** identity, the request fails with **409 Conflict**. - You can handle this error in your integration to surface “duplicate customer” conditions or guide users to reuse the existing identity. Optionally, you can also: - Set a friendly `nickName` and `tags` - Add `validatePayoutRails` to validate the identity against specific payout rails (for example, `US_ACH`, `BR_PIX`, `MX_SPEI`) **Example: Create an INDIVIDUAL ORIGINATOR** ```bash curl -X POST "https://{base-url}/v3/identities" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "identityType": "INDIVIDUAL", "paymentRole": "ORIGINATOR", "internalId": "customer-12345", "nickName": "Alice Sender USD", "tags": ["sender", "priority"], "validatePayoutRails": ["US_ACH"], "individual": { "firstName": "Alice", "lastName": "Chen", "dateOfBirth": "1990-05-14", "citizenship": "US", "address": { "streetAddress": ["123 Main Street"], "city": "San Francisco", "stateOrProvince": "CA", "postalCode": "94105", "country": "US" } } }' ``` **Response (201 Created)** ```json { "identityId": "99254c4f-f207-4792-a846-06928825018c", "version": "1" } ``` Deduplication via internalId If you send another `POST /v3/identities` with the same `internalId` while this identity is still ACTIVE, the service returns: - **409 Conflict** – indicating that another **ACTIVE** identity already uses that `internalId`. Handle 409 as a signal that you should look up and reuse the existing identity instead of creating a duplicate. **Example: Create a BUSINESS BENEFICIARY** ```bash curl -X POST "https://{base-url}/v3/identities" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "identityType": "BUSINESS", "paymentRole": "BENEFICIARY", "internalId": "counterparty-7890", "nickName": "Widgets Org MX", "tags": ["beneficiary", "mx"], "validatePayoutRails": ["MX_SPEI"], "business": { "businessName": "Widgets Org", "incorporationCountry": "US", "registration": [ { "number": "123ABC", "type": "INCORPORATION_CERTIFICATE" } ], "email": "fake@example.com", "phone": "+1234567890", "address": { "streetAddress": ["123 Example MA"], "city": "Boston", "stateOrProvince": "MS", "postalCode": "12345", "country": "US" } } }' ``` **Response (201 Created)** ```json { "identityId": "0116bacc-ffbf-4fa2-a29c-ecd9ea346806", "version": "1" } ``` Missing or invalid PII If any required PII is missing or invalid for the rails in `validatePayoutRails`, the API returns a 400 error describing which fields failed validation. **Example: Validation failure for missing required fields** If you try to create an identity with `validatePayoutRails: ["US_ACH"]` but omit required fields like `dateOfBirth` or `identityDocuments`, you'll receive: **Response (400 Bad Request)** ```json { "code": "VALIDATION_ERROR", "title": "Identity validation failed", "description": "The identity is missing required fields for the specified payout rails.", "details": [ { "field": "individual.dateOfBirth", "message": "Date of birth is required for US_ACH beneficiary identities" }, { "field": "individual.identityDocuments", "message": "At least one identity document (SSN or Tax ID) is required for US_ACH beneficiary identities" } ] } ``` To fix this, add the missing fields and retry the request. ## Handling `internalId` conflicts (409 Conflict) When you create or update an identity with an `internalId` that's already used by another **ACTIVE** identity, the API returns **409 Conflict**. This is a **deduplication feature** that prevents you from accidentally creating multiple identities for the same customer. ### Example: Duplicate `internalId` on create **Request:** ```bash curl -X POST "https://{base-url}/v3/identities" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "identityType": "INDIVIDUAL", "paymentRole": "BENEFICIARY", "internalId": "customer-12345", "individual": { "firstName": "Bob", "lastName": "Smith", "address": { "streetAddress": ["456 Oak Ave"], "city": "Austin", "stateOrProvince": "TX", "postalCode": "78701", "country": "US" } } }' ``` **Response (409 Conflict)** ```json { "code": "CONFLICT", "title": "Identity conflict", "description": "An identity with the provided internalId already exists.", "details": [ { "field": "internalId", "message": "An ACTIVE identity with internalId 'customer-12345' already exists (identityId: 99254c4f-f207-4792-a846-06928825018c)" } ] } ``` ### How to handle 409 Conflict When you receive a 409 Conflict: 1. **Look up the existing identity** using the `identityId` from the error message 2. **Reuse the existing identity** instead of creating a new one 3. **Update the existing identity** if needed (for example, to add new PII or change `validatePayoutRails`) 4. **Investigate your upstream systems** if you're unexpectedly getting conflicts (this may indicate duplicate customer records in your CRM or database) **Example: Look up the existing identity** ```bash curl -X GET "https://{base-url}/v3/identities/99254c4f-f207-4792-a846-06928825018c" \ -H "Authorization: Bearer " ``` If the existing identity is correct, use it for your payment or financial instrument creation. If it needs updates, use `PUT /v3/identities/{identity-id}` to modify it. ### Example: Duplicate `internalId` on update **Request:** ```bash curl -X PUT "https://{base-url}/v3/identities/1d927c62-45fa-4f42-b9f8-8210e1a111bb" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "internalId": "customer-99999" }' ``` If `customer-99999` is already used by a different ACTIVE identity: **Response (409 Conflict)** ```json { "code": "CONFLICT", "title": "Identity conflict", "description": "The provided internalId already exists on a different identity.", "details": [ { "field": "internalId", "message": "An ACTIVE identity with internalId 'customer-99999' already exists (identityId: 7ea3399c-1234-5678-8d8f-d320ea406630)" } ] } ``` ### Best practices for `internalId` conflicts - **Use stable upstream keys** – Use your CRM customer ID, core banking customer ID, or KYC master record ID as `internalId` - **Implement idempotent create logic** – When creating identities, catch 409 errors and look up the existing identity instead of failing - **Avoid PII in `internalId`** – Don't use emails, phone numbers, or national IDs directly; use opaque identifiers - **Monitor conflicts** – Track 409 errors in your logs to identify data quality issues in upstream systems ## Get an identity by ID (and version) Use `GET /v3/identities/{identity-id}` to retrieve a specific identity. - If you omit the version query parameter, the latest version is returned. - To retrieve a specific version, include `?version={version-number}`. **Example: Get the latest version** ```bash curl -X GET "https://{base-url}/v3/identities/9d839f58-7fd3-4913-a27e-48d31973d3f9" \ -H "Authorization: Bearer " ``` **Response (200 OK)** ```json { "identityId": "9d839f58-7fd3-4913-a27e-48d31973d3f9", "identityState": "ACTIVE", "nickName": "nickName", "tags": ["tag1"], "version": "2", "schemaVersion": "1.0.0", "createdAt": "2025-10-01T18:46:41.833Z", "updatedAt": "2025-10-01T18:46:47.430Z", "identityType": "BUSINESS", "paymentRole": "BENEFICIARY", "internalId": "counterparty-7890", "business": { "businessName": "Widgets Org", "address": { "streetAddress": ["123 Example MA"], "country": "US", "city": "Boston", "stateOrProvince": "MS", "postalCode": "12345" }, "email": "fake@example.com", "phone": "+1234567890", "registration": [ { "number": "123ABC", "type": "INCORPORATION_CERTIFICATE" } ], "incorporationCountry": "US" } } ``` **Example: Get a specific version** ```bash curl -X GET "https://{base-url}/v3/identities/9d839f58-7fd3-4913-a27e-48d31973d3f9?version=1" \ -H "Authorization: Bearer " ``` Versioned lookups Use versioned lookups when you need to: - Compare changes over time for audit - Reconcile payments that used an earlier version of an identity If the identity or version does not exist, the service returns **404 Not Found**. ## List identities Use `GET /v3/identities` to retrieve identities for your organization with optional filters and pagination. Supported query parameters: - `payment-role` – filter by payment role (ORIGINATOR or BENEFICIARY) - `nick-name` – filter by nickname (exact match) - `limit` – maximum number of identities to return (1–100, default 10) - `next-token` – opaque token returned from a previous response, used to fetch the next page **Example: List identities by role** ```bash curl -X GET "https://{base-url}/v3/identities?payment-role=BENEFICIARY&limit=2" \ -H "Authorization: Bearer " ``` **Response (200 OK)** ```json { "data": [ { "identityId": "0116bacc-ffbf-4fa2-a29c-ecd9ea346806", "identityType": "BUSINESS", "paymentRole": "BENEFICIARY", "createdAt": "2025-10-01T16:14:13.200Z", "updatedAt": "2025-10-01T16:14:15.763Z", "identityState": "ACTIVE", "nickName": "testNickName", "tags": ["tag1"], "version": 2, "schemaVersion": "1.0.0", "internalId": "counterparty-7890" }, { "identityId": "1d927c62-45fa-4f42-b9f8-8210e1a111bb", "identityType": "INDIVIDUAL", "paymentRole": "ORIGINATOR", "createdAt": "2025-10-01T17:20:05.111Z", "updatedAt": "2025-10-01T17:20:06.500Z", "identityState": "ACTIVE", "nickName": "Alice Originator", "tags": ["sender", "priority"], "version": 1, "schemaVersion": "1.0.0", "internalId": "customer-12345" } ], "nextToken": "eyJrZXkxIjoidmFsdWUifQ==" } ``` If you receive a `nextToken`, pass it back as `next-token` to fetch the next page: ```bash curl -X GET "https://{base-url}/v3/identities?payment-role=BENEFICIARY&limit=2&next-token=eyJrZXkxIjoidmFsdWUifQ==" \ -H "Authorization: Bearer " ``` No identities found If no identities match your filters, the service returns **404 Not Found**. ## Update an identity Use `PUT /v3/identities/{identity-id}` to update an existing identity. Each successful update creates a **new identity version**. The `identityId` never changes, and previous versions remain available for retrieval and audit. The request body supports **partial updates** — only include the fields you want to change. You can update: - Metadata: `nickName`, `tags` - PII: any field within `individual` or `business` - `internalId` (must remain unique across all ACTIVE identities) - `validatePayoutRails` — re-validates PII against the specified rails `identityType` and `paymentRole` cannot be changed after creation. For complete examples, error handling, and guidance on updating BLOCKED identities, see [Update an identity](/products/payments-direct-2/api-docs/tutorials/update-an-identity). ## Deactivate an identity Use `DELETE /v3/identities/{identity-id}` to deactivate an identity. Deactivation: - Sets the identity’s state to **DEACTIVATED** - Deactivates the identity’s associated **financial instruments** - Prevents any **new payments** from using this identity - Keeps historical versions available for audit Permanent Deactivation is **permanent**; you cannot reactivate a deactivated identity. **Example: Deactivate an identity** ```bash curl -X DELETE "https://{base-url}/v3/identities/146f3c51-c313-47ce-b6f2-691c5a238b3e" \ -H "Authorization: Bearer " ``` **Response (204 No Content) – the identity is successfully deactivated.** Error responses: - **400 Bad Request** – malformed request or invalid `identityId` format - **404 Not Found** – identity does not exist - **422 Unprocessable Entity** – identity is already deactivated or cannot be deactivated (for example, due to internal constraints) ## Error handling and validation All identity endpoints use a standard error response schema with: - **code** – machine-readable error code - **title** – short human-readable summary - **description** – detailed explanation and remediation hints Common cases: - **400** – missing or invalid fields in the request, or identity failing corridor-specific validation rules - **404** – identity not found (for a specific ID, version, or query) - **409** – `internalId` conflict (another ACTIVE identity already uses the same `internalId`) - **422** – invalid lifecycle transition (for example, attempting to deactivate an already deactivated identity) - **500** – internal processing error; retry or contact support if persistent Refer to the general [Error handling](/products/payments-direct-2/api-docs/error-codes/api-errors) section of the API docs for the full list of error codes. ## Next steps - For a deeper explanation of the identity model, see [Payment identities](/products/payments-direct-2/api-docs/concepts/payment-identities). - For full update examples and guidance on immutable versioning, payout rail validation, and BLOCKED identity reactivation, see [Update an identity](/products/payments-direct-2/api-docs/tutorials/update-an-identity). - To learn how to attach payout account details to identities (bank accounts, wallets, local payout rails), continue with [Create and manage financial instruments](/products/payments-direct-2/api-docs/tutorials/create-and-manage-financial-instruments).