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
codeandtypefields - 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
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.
| Type | Prefix | What it means | General action |
|---|---|---|---|
USER_ERROR or NOT_FOUND | USR_ | 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_ERROR | AUTH_ | The request was rejected due to an authentication or authorization problem. | See Handling authentication errors below. |
SYSTEM_ERROR | SYS_ | An internal error occurred in Ripple's infrastructure. | Retry with exponential backoff. Escalate to Ripple technical support if the condition persists. |
CONFIGURATION_ERROR | CFG_ | An account or service configuration issue prevents the request from completing. | Do not retry. Contact Ripple technical support. |
Before retrying, confirm that the error is transient (a condition that may resolve itself) rather than permanent.
| Error type | HTTP status | Retryable? | Recommended action |
|---|---|---|---|
USER_ERROR | 400, 404, 415 | No | Fix the request and resubmit. |
USER_ERROR | 402 | Not until resolved | Resolve the underlying condition (insufficient balance, credit limit, or past-due invoice) before retrying. |
NOT_FOUND | 404 | No | Verify the resource ID and resubmit. |
AUTH_ERROR | 401 | Yes (after token refresh) | Regenerate your access token and retry. |
AUTH_ERROR | 403 | No | Verify your token has the required scopes. Contact Ripple technical support if the issue persists. |
SYSTEM_ERROR | 500 | Yes (with backoff) | Retry with exponential backoff. Contact Ripple technical support if the issue persists. |
CONFIGURATION_ERROR | 500 | No | Contact Ripple technical support. |
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:
- On the first failure, wait a short base interval.
- On each subsequent failure, double the wait time.
- Add random jitter (±10–20% of the interval) to desynchronize retries across clients.
- Cap the maximum wait at a reasonable ceiling.
- After a configurable number of attempts, stop retrying and alert your on-call team.
Example backoff schedule:
| Retry attempt | Base wait | With jitter (±20%) |
|---|---|---|
| 1 | 2s | 1.6s – 2.4s |
| 2 | 4s | 3.2s – 4.8s |
| 3 | 8s | 6.4s – 9.6s |
| 4 | 16s | 12.8s – 19.2s |
| 5 | 32s | 25.6s – 38.4s |
| 6+ | 60s (cap) | 48s – 72s |
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.
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.
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.
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.
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
internalIdfilter is not an exact-match lookup. Confirm that theinternalIdon 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.
A 401 error typically means your access token is expired or invalid. Access tokens have a 1-hour TTL.
Recommended handling:
- Generate a new access token using your
client_idandclient_secret. - Retry the original request with the new token.
- Do not cache tokens beyond their
expires_invalue.
For guidance on token generation and caching, see Authentication.
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.
AUTH_051 occurs when a request references a payment created by a different organization. This is a permanent error. Do not retry.
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.
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.
| State | Retryable? | Action |
|---|---|---|
FAILED | Possibly | Fetch 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. |
DECLINED | Not until resolved | Fetch 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. |
RETURNED | Consult Ripple | The 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.
Log the following fields from every error response:
code— the specific error code; use this for alerting rules and dashboardstype— the error category; use this to route to the correct handlerstatus— the HTTP status codetimestamp— 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)
| Condition | Alert priority |
|---|---|
Any CONFIGURATION_ERROR (CFG_) | High — these require Ripple involvement and will not self-resolve |
Repeated SYSTEM_ERROR (SYS_) from the same endpoint | Medium — 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 |
- For the complete list of error codes, descriptions, and HTTP statuses, see API error codes.
- For payment-specific failure codes, see API payment failures.
- For guidance on tracking payment state changes without polling, see Notification webhooks.
- To poll for payment updates instead of using webhooks, see Polling.
- For access token generation and caching details, see Authentication.
- For payment state definitions and lifecycle details, see Payment states.