Skip to content

Error handling and retry strategy

Payments Direct returns a standardized error response for every failed API request. Building a reliable integration means classifying errors correctly, retrying safely, and escalating promptly when retrying won't help.

This topic covers:

  • How to classify errors using the code and type fields
  • Which errors are safe to retry, and how to retry them
  • Recommended exponential backoff parameters
  • Handling authentication errors
  • Handling payment failures (FAILED, DECLINED, RETURNED states)
  • What to log and monitor

Classify before you act

Every error response includes a code field (for example, USR_067) and a type field (for example, USER_ERROR). Always use the code field as your primary signal, not the HTTP status code alone. Two errors can share an HTTP status but require completely different handling.

Error type reference

TypePrefixWhat it meansGeneral action
USER_ERROR or NOT_FOUNDUSR_The request itself is the problem: missing fields, invalid values, or a resource that doesn't exist.Fix the request and resubmit. Do not retry the same request.
AUTH_ERRORAUTH_The request was rejected due to an authentication or authorization problem.See Handling authentication errors below.
SYSTEM_ERRORSYS_An internal error occurred in Ripple's infrastructure.Retry with exponential backoff. Escalate to Ripple technical support if the condition persists.
CONFIGURATION_ERRORCFG_An account or service configuration issue prevents the request from completing.Do not retry. Contact Ripple technical support.

Transient vs. permanent errors

Before retrying, confirm that the error is transient (a condition that may resolve itself) rather than permanent.

Error typeHTTP statusRetryable?Recommended action
USER_ERROR400, 404, 415NoFix the request and resubmit.
USER_ERROR402Not until resolvedResolve the underlying condition (insufficient balance, credit limit, or past-due invoice) before retrying.
NOT_FOUND404NoVerify the resource ID and resubmit.
AUTH_ERROR401Yes (after token refresh)Regenerate your access token and retry.
AUTH_ERROR403NoVerify your token has the required scopes. Contact Ripple technical support if the issue persists.
SYSTEM_ERROR500Yes (with backoff)Retry with exponential backoff. Contact Ripple technical support if the issue persists.
CONFIGURATION_ERROR500NoContact Ripple technical support.

Retry strategy

Exponential backoff with jitter

When a request fails with a retryable error (SYSTEM_ERROR / 500, or AUTH_ERROR / 401 after a token refresh), use exponential backoff with jitter rather than retrying immediately or at a fixed interval. Immediate or synchronized retries amplify load on an already stressed system and can trigger rate limiting.

Recommended approach:

  1. On the first failure, wait a short base interval.
  2. On each subsequent failure, double the wait time.
  3. Add random jitter (±10–20% of the interval) to desynchronize retries across clients.
  4. Cap the maximum wait at a reasonable ceiling.
  5. After a configurable number of attempts, stop retrying and alert your on-call team.

Example backoff schedule:

Retry attemptBase waitWith jitter (±20%)
12s1.6s – 2.4s
24s3.2s – 4.8s
38s6.4s – 9.6s
416s12.8s – 19.2s
532s25.6s – 38.4s
6+60s (cap)48s – 72s

Use a maximum retry count

Set a hard limit on the number of retries (for example, 5 attempts). After that limit is reached without a successful response, stop retrying, record the failure, and alert your team. Continuing to retry indefinitely can mask a persistent issue and delay investigation.


Idempotency and safe retries

A timeout or connection error on a create payment request does not mean the payment was not created. Ripple may have received and processed the request even though you did not receive the response.

The quoteId is the idempotency key

Payment creation is deduplicated on quoteId, and one quote can produce at most one payment. If a payment already exists for the quoteId in a POST /v3/payments request, Ripple returns that payment instead of creating a new one.

When you resend a request for a quote that already has a payment:

  • The response is 201 Created, the same as for a new payment. The status code does not tell you whether this request created the payment or returned an existing one.
  • The payment is returned as it was stored. Ripple does not compare the rest of the request body against it, so changing a field on a resend has no effect. Resend the original request unchanged.
  • The payment is returned even if the quote has expired since the first request.

internalId is not an idempotency key for payments. Ripple does not check payment internalId values for uniqueness, so two requests with the same internalId and different quoteId values create two payments. This differs from identities, where internalId must be unique across your ACTIVE identities. See Payment identities.

Retrying after a timeout

Retry with the same quoteId, never a new quote

If a create payment request times out or fails with a connection error, resend it with the same quoteId. If the first request created the payment, the resend returns it. If it did not, the resend creates it.

Do not get a new quote and submit it in place of the first. A new quoteId is a new payment, and Ripple cannot tell that it duplicates the first one, even if both requests carry the same internalId.

Space resends out using the backoff described in Retry strategy.

If a resend is rejected with HTTP 400 and USR_058 (quote expired), no payment was stored for that quote when the resend was processed. Before you get a new quote, check that the original request did not create a payment, as described below.

Checking whether a payment was created

To reconcile payments against your own records, search with POST /v3/payments/filter and your internalId. This is still a valid check, with two limits:

  • Search results are updated asynchronously. A payment created moments ago may not appear yet, so an empty result immediately after a timeout does not prove that the payment was not created.
  • The internalId filter is not an exact-match lookup. Confirm that the internalId on each returned payment matches yours exactly.

Because of these limits, resending with the same quoteId is the reliable way to recover from a timeout. Use the search to reconcile, not to decide whether to resend.


Handling authentication errors

401 Unauthorized (AUTH_001, AUTH_003)

A 401 error typically means your access token is expired or invalid. Access tokens have a 1-hour TTL.

Recommended handling:

  1. Generate a new access token using your client_id and client_secret.
  2. Retry the original request with the new token.
  3. Do not cache tokens beyond their expires_in value.

For guidance on token generation and caching, see Authentication.

403 Forbidden (AUTH_002)

A 403 error means your token is valid but lacks the required scopes for the requested operation.

Recommended handling:

  • Do not retry with the same token. A new token with the same credentials will have the same scopes.
  • Review the required scopes for the operation in the API reference.
  • Contact Ripple technical support if you believe your credentials should have the required permissions.

403 Unauthorized (AUTH_051)

AUTH_051 occurs when a request references a payment created by a different organization. This is a permanent error. Do not retry.


Handling payment failures

Payment failures are distinct from API errors. A payment failure occurs after a payment is created successfully (HTTP 201) but later transitions to a terminal state of FAILED, DECLINED, or RETURNED. These are not errors in the API response. They are paymentState values you observe when polling or receiving webhooks.

How to detect a payment failure

Payment failure details, including the error code and reason, are available on the payment object at GET /v3/payments/{paymentId} after the payment reaches a terminal state. Webhook payloads include the new paymentState but not the error code. Always fetch the full payment record to get failure details.

Terminal states

StateRetryable?Action
FAILEDPossiblyFetch the payment record to get the error code. Check the API payment failures reference and API error codes reference for the specific code. Some FAILED states are caused by transient issues and can be retried with a new payment; others indicate a permanent condition.
DECLINEDNot until resolvedFetch the payment record to get the error code. DECLINED usually indicates a business rule violation or account condition (insufficient balance, credit limit, etc.) that must be resolved before resubmitting.
RETURNEDConsult RippleThe payment was returned by a downstream institution after completing. This is not a retry scenario. Contact Ripple technical support to investigate the return reason.

For a full list of payment failure codes and their descriptions, see API payment failures.

For background on payment states and the payment lifecycle, see Payment states.


What to log and monitor

Log the following fields from every error response:

  • code — the specific error code; use this for alerting rules and dashboards
  • type — the error category; use this to route to the correct handler
  • status — the HTTP status code
  • timestamp — the time the error occurred in the upstream system
  • The API endpoint and HTTP method that returned the error
  • Your request ID or correlation ID (if your integration generates one)

Alerting recommendations

ConditionAlert priority
Any CONFIGURATION_ERROR (CFG_)High — these require Ripple involvement and will not self-resolve
Repeated SYSTEM_ERROR (SYS_) from the same endpointMedium — investigate after retry budget is exhausted
AUTH_ERROR 403 (AUTH_002, AUTH_051)Medium — may indicate a configuration change or credential scope issue
Sustained USER_ERROR 402 (USR_062–USR_067)Medium — indicates an account condition (balance, limits, invoice) requiring attention

Next steps