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:
- Create a quote collection for a proposed payment.
- Choose a quote from the collection.
- 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)
- Get payment state transitions to confirm the payment reached
COMPLETED.
For more about payment identities and financial instruments, see Payment identities and Financial instruments.
This tutorial assumes the recommended Identity Management v3 model (separate identities and financial instruments).
If you are still using v2 identities, see the Create a v2 payment and Create a v2 third-party payment tutorials.
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.
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.
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.
POST /v2/quotes/quote-collection
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).
curl -i -X POST \
https://api.test.ripple.com/v2/quotes/quote-collection \
-H "Authorization: Bearer <YOUR_JWT_HERE>" \
-H "Content-Type: application/json" \
-d '{
"quoteAmount": 10000,
"quoteAmountType": "SOURCE_AMOUNT",
"sourceCurrency": "USD",
"destinationCurrency": "MXN",
"sourceCountry": "US",
"destinationCountry": "MX",
"payoutCategory": "BANK",
"payinCategory": "FUNDED"
}'{
"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"
}
]
}quoteStatusshould be ACTIVE.- Note the
quoteIdyou want to use; you’ll pass it into the Create payment operation.
If the response contains multiple quotes (for example, different routes or FX options), your application logic should:
- Iterate through
quotes[]in the response. - Apply your criteria (best FX rate, lowest fee, preferred payout configuration).
- Select a single
quoteIdfor the Create payment step.
For the rest of this tutorial, we’ll assume you select:
quoteId = 7ea3399c-1234-5678-8d8f-d320ea406630
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.
paymentLabels are simple strings you can use to categorize and search payments. A common pattern is to store key=value pairs.
For example:
"paymentLabels": [
"customerSegment=PREMIUM",
"invoiceNumber=INV-2025-0615"
]These labels can later be updated via the Update payment labels operation.
POST /v3/payments
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 usekey=valuepairs.- Other optional fields such as
internalId,purposeCode, andsourceOfCashmay be available depending on your configuration.
- First-party: You are the originator;
originatorIdentityIdis either omitted or set to your own identity. - Third-party: You send on behalf of a customer;
originatorIdentityIdmust be set to that customer’s identity token.
We’ll show both first-party and third-party examples side by side.
In this example, you are the originator. You omit originatorIdentityId (or use your own identity).
curl -i -X POST \
https://api.test.ripple.com/v3/payments \
-H "Authorization: Bearer <YOUR_JWT_HERE>" \
-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"
]
}'If your tenant is still using v2 identities (where identity and financial instrument data are stored together):
- Set only
beneficiaryIdentityIdto 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:
"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.
On success, the API returns a 201 response with details about the payment and a paymentId you can use to monitor it.
{
"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"
}paymentId = aa74f2f4-5996-4f0c-9d8a-7a5e1d51c502
You’ll use this in later steps.
To check the current status of a payment or retrieve its details, call the Get payment by ID operation.
GET /v3/payments/{paymentId}
curl -i -X GET \
https://api.test.ripple.com/v3/payments/aa74f2f4-5996-4f0c-9d8a-7a5e1d51c502 \
-H "Authorization: Bearer <YOUR_JWT_HERE>"{
"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"
}
}- Inspect
paymentStateto see where the payment is in the lifecycle. - Persist the response for your own auditing or internal dashboards.
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
curl -i -X GET \
https://api.test.ripple.com/v3/payments/aa74f2f4-5996-4f0c-9d8a-7a5e1d51c502/states \
-H "Authorization: Bearer <YOUR_JWT_HERE>"{
"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.
- Each object in
stateTransitions[]records a change inpaymentState. - Treat the payment as successfully completed when the last transition’s updatedTo value is
COMPLETED. - Other terminal states may include
FAILEDorRETURNED, depending on corridor and payout network behavior. - If the payment is rejected or returned, you will see terminal states such as
DECLINEDorRETURNEDinstead ofCOMPLETED.
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 section of the Payments Direct API docs for a complete list of error codes.
In this tutorial, you:
- Created a quote collection to price a cross-border payment.
- Selected a quote and used its
quoteIdto create a payment with the v3 operation, including:beneficiaryIdentityIdbeneficiaryFinancialInstrumentId- Optional
originatorIdentityIdfor third-party payments
- 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
originatorIdentityIdandbeneficiaryIdentityId, see Create and manage identities. - To learn how to create, update, and deactivate the bank accounts referenced by
beneficiaryFinancialInstrumentId, see Create and manage financial instruments.