Skip to content

Create a payment

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 and Financial instruments.

Customers using legacy v2 identities

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.

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

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"
  }'

Example response (truncated)

{
  "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:

"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.

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"
    ]
  }'
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:

"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.

{
  "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.


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

curl -i -X GET \
  https://api.test.ripple.com/v3/payments/aa74f2f4-5996-4f0c-9d8a-7a5e1d51c502 \
  -H "Authorization: Bearer <YOUR_JWT_HERE>"

Example response (truncated)

{
  "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

curl -i -X GET \
  https://api.test.ripple.com/v3/payments/aa74f2f4-5996-4f0c-9d8a-7a5e1d51c502/states \
  -H "Authorization: Bearer <YOUR_JWT_HERE>"

Example response

{
  "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 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: