# Create a payment 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 In this tutorial, you'll walk through the steps required to create a payment using the Ripple Payments Direct 2.0 API with the new **v3 Payments endpoint**: 1. Create a quote collection for a proposed payment. 2. Choose a quote from the collection. 3. Create a payment using the **v3 payments operation**, including: - `beneficiaryIdentityId` (ID of the beneficiary’s payment identity) - `beneficiaryFinancialInstrumentId` (ID of the beneficiary’s financial instrument / payout method) - (Optionally) `originatorIdentityId` (ID of the originator’s payment identity, for third-party payments) 4. Get payment state transitions to confirm the payment reached `COMPLETED`. For more about payment identities and financial instruments, see [Payment identities](/products/payments-direct-2/api-docs/concepts/payment-identities) and [Financial instruments](/products/payments-direct-2/api-docs/concepts/financial-instruments). Customers using legacy v2 identities This tutorial assumes the recommended **Identity Management v3** model (separate identities for originators and beneficiaries). ## Before you begin To follow this tutorial, you should have: - Access to the Ripple Payments Direct 2.0 API UAT environment. - A valid OAuth2 **access token** with scopes for quotes and payments. - At least one **originator identity** (and optionally an identity for third-party originator flows). - At least one **beneficiary identity** and one or more **financial instruments** for that beneficiary. For customers using legacy v2 identities If you are still using legacy v2 identities, make sure you know the v2 `identityId` for the beneficiary. You will use that ID directly in the payment request and omit the financial instrument ID. ## Step 1: Create a quote collection First, request a **quote collection** that describes how much your beneficiary will receive, the FX rate, fees, and when the quote expires. In this example, a sender is sending **10,000 USD** to a beneficiary in **Mexico**, who will receive **MXN** into a bank account. ### Endpoint `POST /v2/quotes/quote-collection` ### Request body parameters At minimum, provide: - `quoteAmount` – The amount you want to quote. - `quoteAmountType` – Whether quoteAmount is a source or destination amount (for example, SOURCE_AMOUNT). - `sourceCurrency` / `destinationCurrency` – The send and receive currencies. - `sourceCountry` / `destinationCountry` – The originator and beneficiary countries (ISO 3166-1 alpha-2 codes). - `payoutCategory` – How the beneficiary receives funds (for example, BANK). - `payinCategory` – How you fund the payment (for example, FUNDED). ### Example request ```bash curl -i -X POST \ https://api.test.ripple.com/v2/quotes/quote-collection \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "quoteAmount": 10000, "quoteAmountType": "SOURCE_AMOUNT", "sourceCurrency": "USD", "destinationCurrency": "MXN", "sourceCountry": "US", "destinationCountry": "MX", "payoutCategory": "BANK", "payinCategory": "FUNDED" }' ``` ### Example response (truncated) ```json { "quoteCollectionId": "11111111-aaaa-2222-bbbb-222222222222", "quotes": [ { "quoteId": "7ea3399c-1234-5678-8d8f-d320ea406630", "quoteStatus": "ACTIVE", "quoteAmountType": "SOURCE_AMOUNT", "sourceAmount": 10000, "destinationAmount": 204533.30, "sourceCurrency": "USD", "destinationCurrency": "MXN", "sourceCountry": "US", "destinationCountry": "MX", "payoutCategory": "BANK", "payinCategory": "FUNDED", "adjustedExchangeRate": { "adjustedRate": 20.4136 }, "fees": [ { "totalFee": 14, "feeCurrency": "USD" } ], "createdAt": "2025-11-13T22:44:34.711Z", "expiresAt": "2025-11-13T22:59:34.767Z" } ] } ``` What to check - `quoteStatus` should be ACTIVE. - Note the `quoteId` you want to use; you’ll pass it into the Create payment operation. ## Step 2: Choose a quote If the response contains multiple quotes (for example, different routes or FX options), your application logic should: 1. Iterate through `quotes[]` in the response. 2. Apply your criteria (best FX rate, lowest fee, preferred payout configuration). 3. Select a single `quoteId` for the Create payment step. For the rest of this tutorial, we’ll assume you select: `quoteId = 7ea3399c-1234-5678-8d8f-d320ea406630` ## Step 3: Create a payment with the v3 payments operation Once you’ve selected a quote, you can create a payment using the v3 payments operation. This is where you connect: - The quote (`quoteId`) - The beneficiary identity (`beneficiaryIdentityId`) - The beneficiary financial instrument (`beneficiaryFinancialInstrumentId`) - **(Optionally)** the originator identity (`originatorIdentityId`) if you are sending on behalf of a customer This lets the same beneficiary hold multiple bank accounts under one identity; you select the correct account by choosing the appropriate `beneficiaryFinancialInstrumentId`. ### Payment labels `paymentLabels` are simple strings you can use to categorize and search payments. A common pattern is to store key=value pairs. For example: ```json "paymentLabels": [ "customerSegment=PREMIUM", "invoiceNumber=INV-2025-0615" ] ``` These labels can later be updated via the **Update payment labels** operation. ### Endpoint `POST /v3/payments` ### Required and optional fields Key requirede fields in the v3 payment request: - `quoteId` – The quoteId from the quote you selected. - `beneficiaryIdentityId` – The identity token for the beneficiary. - `beneficiaryFinancialInstrumentId` – The financial instrument token for the beneficiary’s payout account. **Optional but commonly used:** - `originatorIdentityId` – Include for third-party payments where you send on behalf of a business or institution. Omit (or use your own identity) when you are the originator. - `receiverRelationship` – Relationship between originator and beneficiary (for example, FAMILY, SUPPLIER). - `paymentMemo` – Free-text memo used for reconciliation; not stored in PII. - `paymentLabels` – Application-defined string labels for grouping and categorizing payments (for example, `customerSegment=PREMIUM`). Labels are stored as simple strings; a common convention is to use `key=value` pairs. - Other optional fields such as `internalId`, `purposeCode`, and `sourceOfCash` may be available depending on your configuration. First-party vs third-party payments - **First-party:** You are the originator; `originatorIdentityId` is either omitted or set to your own identity. - **Third-party:** You send on behalf of a customer; `originatorIdentityId` must be set to that customer’s identity token. ### Example requests We’ll show both first-party and third-party examples side by side. First-party payment In this example, you are the originator. You omit `originatorIdentityId` (or use your own identity). ```bash curl -i -X POST \ https://api.test.ripple.com/v3/payments \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "quoteId": "7ea3399c-1234-5678-8d8f-d320ea406630", "beneficiaryIdentityId": "7ea3399c-1234-5678-8d8f-d320ea406630", "beneficiaryFinancialInstrumentId": "0e0d7b5a-7f2b-4c75-9bb9-8c4d0ff5f2a1", "receiverRelationship": "SUPPLIER", "paymentMemo": "INVOICE 2025-0615", "paymentLabels": [ "customerSegment=PREMIUM", "invoiceNumber=INV-2025-0615" ] }' ``` For customers using legacy v2 identities If your tenant is still using v2 identities (where identity and financial instrument data are stored together): - Set only `beneficiaryIdentityId` to your existing v2 identity ID. - Do not set `beneficiaryFinancialInstrumentId` (leave it blank or omit the field). - The Payments service will use the financial details embedded in the v2 identity for routing and payout. **Example:** ```json "beneficiary": { "beneficiaryIdentityId": "id-v2-identity-uuid" // beneficiaryFinancialInstrumentId omitted for PII v2 compatibility } ``` All other fields in the payment request (quote, amounts, purpose, memo, etc.) remain the same as shown in this tutorial. Third-party payment In this example, you are sending on behalf of a corporate customer. You must include `originatorIdentityId`. ```bash curl -i -X POST \ https://api.test.ripple.com/v3/payments \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "quoteId": "7ea3399c-1234-5678-8d8f-d320ea406630", "originatorIdentityId": "c1e92b47-4579-4a7e-9c9a-02f3e3e4bb11", "beneficiaryIdentityId": "7ea3399c-1234-5678-8d8f-d320ea406630", "beneficiaryFinancialInstrumentId": "0e0d7b5a-7f2b-4c75-9bb9-8c4d0ff5f2a1", "receiverRelationship": "SUPPLIER", "paymentMemo": "INVOICE 2025-0615", "paymentLabels": [ "customerSegment=PREMIUM", "invoiceNumber=INV-2025-0615" ] }' ``` For customers using legacy v2 identities If your tenant is still using v2 identities (where identity and financial instrument data are stored together): - Set only `beneficiaryIdentityId` to your existing v2 identity ID. - Do not set `beneficiaryFinancialInstrumentId` (leave it blank or omit the field). - The Payments service will use the financial details embedded in the v2 identity for routing and payout. **Example:** ```json "beneficiary": { "beneficiaryIdentityId": "id-v2-identity-uuid" // beneficiaryFinancialInstrumentId omitted for PII v2 compatibility } ``` All other fields in the payment request (quote, amounts, purpose, memo, etc.) remain the same as shown in this tutorial. ### Example response On success, the API returns a 201 response with details about the payment and a `paymentId` you can use to monitor it. ```json { "paymentId": "aa74f2f4-5996-4f0c-9d8a-7a5e1d51c502", "quoteId": "7ea3399c-1234-5678-8d8f-d320ea406630", "paymentState": "INITIATED", "receiverRelationship": "SUPPLIER", "paymentMemo": "INVOICE 2025-0615", "paymentLabels": [ "customerSegment=PREMIUM", "invoiceNumber=INV-2025-0615" ], "originator": { "originatorIdentityId": "c1e92b47-4579-4a7e-9c9a-02f3e3e4bb11", "sourceCurrency": "USD", "sourceAmount": "10000.00", "sourceCountry": "US" }, "destination": { "beneficiaryIdentityId": "7ea3399c-1234-5678-8d8f-d320ea406630", "beneficiaryFinancialInstrumentId": "0e0d7b5a-7f2b-4c75-9bb9-8c4d0ff5f2a1", "destinationCurrency": "MXN", "destinationAmount": "204533.30", "destinationCountry": "MX" }, "fees": { "totalFeesAmount": "14.00", "totalFeesCurrency": "USD" }, "createdAt": "2025-03-15T10:23:45Z", "initiatedAt": "2025-03-15T10:23:45Z", "expiresAt": "2025-03-15T10:28:45Z" } ``` The key value to capture is: `paymentId = aa74f2f4-5996-4f0c-9d8a-7a5e1d51c502` You’ll use this in later steps. ## Step 4: Get a payment by ID (optional but recommended) To check the current status of a payment or retrieve its details, call the **Get payment by ID** operation. ### Endpoint `GET /v3/payments/{paymentId}` ### Example request ```bash curl -i -X GET \ https://api.test.ripple.com/v3/payments/aa74f2f4-5996-4f0c-9d8a-7a5e1d51c502 \ -H "Authorization: Bearer " ``` ### Example response (truncated) ```json { "paymentId": "aa74f2f4-5996-4f0c-9d8a-7a5e1d51c502", "initiatedAt": "2025-03-15T10:23:45Z", "expiresAt": "2025-03-15T10:28:45Z", "lastStateUpdatedAt": "2025-03-15T10:24:15Z", "paymentState": "TRANSFERRING", "originator": { "originatorIdentityId": "c1e92b47-4579-4a7e-9c9a-02f3e3e4bb11", "sourceCountry": "US", "sourceCurrency": "USD", "sourceAmount": 10000, "payin": "FUNDED" }, "destination": { "destinationAmount": 204533.30, "destinationCountry": "MX", "destinationCurrency": "MXN", "beneficiaryIdentityId": "7ea3399c-1234-5678-8d8f-d320ea406630", "beneficiaryFinancialInstrumentId": "0e0d7b5a-7f2b-4c75-9bb9-8c4d0ff5f2a1", "payout": "BANK" } } ``` You can use this endpoint to: - Inspect `paymentState` to see where the payment is in the lifecycle. - Persist the response for your own auditing or internal dashboards. ## Step 5: Get state transitions to confirm completion To see how a payment moved through its lifecycle, especially to confirm that it reached `COMPLETED`, use the **Get state transitions** operation. Endpoint `GET /v3/payments/{paymentId}/states` ### Example request ```bash curl -i -X GET \ https://api.test.ripple.com/v3/payments/aa74f2f4-5996-4f0c-9d8a-7a5e1d51c502/states \ -H "Authorization: Bearer " ``` ### Example response ```json { "stateTransitions": [ { "updatedFrom": "QUOTED", "updatedTo": "INITIATED", "updatedAt": "2025-03-15T10:23:45Z" }, { "updatedFrom": "INITIATED", "updatedTo": "VALIDATING", "updatedAt": "2025-03-15T10:23:47Z" }, { "updatedFrom": "VALIDATING", "updatedTo": "TRANSFERRING", "updatedAt": "2025-03-15T10:23:55Z" }, { "updatedFrom": "TRANSFERRING", "updatedTo": "COMPLETED", "updatedAt": "2025-03-15T10:25:12Z" } ] } ``` Your application can: - Poll this endpoint until the latest state is COMPLETED, or - Use an eventing/webhook mechanism if available in your environment. Interpreting state transitions - Each object in `stateTransitions[]` records a change in `paymentState`. - Treat the payment as successfully completed when the last transition’s updatedTo value is `COMPLETED`. - Other terminal states may include `FAILED` or `RETURNED`, depending on corridor and payout network behavior. - If the payment is rejected or returned, you will see terminal states such as `DECLINED` or `RETURNED` instead of `COMPLETED`. ## Error handling Common errors in this workflow include: - **400 Bad Request** – malformed request body, missing required fields, or invalid combinations (for example, incompatible currency/rail). - **404 Not Found** – non-existent quote collection, quote, or payment ID. - **409 Conflict** – conflicting changes or business constraints (for example, using an inactive identity or instrument). - **422 Unprocessable Entity** – payment cannot proceed due to corridor-specific validation failures. - **500 Internal Server Error** – unexpected server-side issues; retry or contact support if persistent. Payment-related errors are returned in a standard error schema that includes: - **code** – machine-readable error code - **title** – short human-readable summary - **description** – more detailed explanation and remediation hints Refer to the [Error handling](/products/payments-direct-2/api-docs/error-codes/api-errors) section of the Payments Direct API docs for a complete list of error codes. ## Summary and next steps In this tutorial, you: 1. Created a quote collection to price a cross-border payment. 2. Selected a quote and used its `quoteId` to create a payment with the v3 operation, including: - `beneficiaryIdentityId` - `beneficiaryFinancialInstrumentId` - Optional `originatorIdentityId` for third-party payments 1. Retrieved the payment and then its state transitions to confirm that it reached `COMPLETED`. **Next, you can:** - To learn how to create and manage the identities referenced by `originatorIdentityId` and `beneficiaryIdentityId`, see [Create and manage identities](/products/payments-direct-2/api-docs/tutorials/create-and-manage-identities). - To learn how to create, update, and deactivate the bank accounts referenced by `beneficiaryFinancialInstrumentId`, see [Create and manage financial instruments](/products/payments-direct-2/api-docs/tutorials/create-and-manage-financial-instruments).