# Create a payment Identity Management v3 This topic describes the v3 identity model. **Identity Management v3** is recommended for all new integrations. The supported payment corridors are: - US • USD - EU • EUR - MX • MXN - BR • BRL - CO • COP - CA • CAD - GB • GBP - NG • NGN - SWIFT • USD For migration details or questions, contact your Ripple representative. 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 (`PRE_FUNDING`, `CREDIT_FUNDING`, or `JIT_FUNDING`). See [Funding model](/products/payments-direct-2/api-docs/concepts/quotes#funding-model-payincategory) for details. ### 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": "PRE_FUNDING" }' ``` ### 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": "PRE_FUNDING", "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. JIT-funded payments If you used `payinCategory: JIT_FUNDING` in your quote, the payment will initially be in `AWAITING_FUNDING` state rather than `INITIATED`. The response will include a `jitFundingExpiresAt` timestamp. You must transfer the required funds to your Ripple ledger account before that time for the payment to proceed. If the deadline passes without funding, the payment expires and you must create a new quote and payment. ## 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": "PRE_FUNDING" }, "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).