Introduction
XPay surfaces failures in two distinct places: the response body of an API call you made, and the lastPaymentError field on a payment that failed. Pick the surface that matches your problem.
XPay surfaces failures in two distinct places, and they're handled with two different patterns. Pick the one that matches what just went wrong:
- An API call your server made returned a non-2xx. The error is in the HTTP response body. You handle it where the call happens. → API errors.
- A customer's payment failed. The error sits on the
lastPaymentErrorfield of the Payment Intent for the rest of its life. You read it after the payment attempt, in your webhook handler or in the dashboard. → Payment errors.
These two surfaces don't share a code list and don't share a documentation URL namespace. The rest of the Errors group is split along this line.
The two surfaces at a glance
| API errors | Payment errors | |
|---|---|---|
| When you encounter it | Synchronously, as the response to an API call | Asynchronously, on the Payment Intent that failed |
| Where it lives | The HTTP response body: { error: { type, code, ... }, request_id } | The Payment Intent's lastPaymentError field |
| What caused it | Your request was rejected (validation, auth, missing resource, conflict) | The customer's card was declined, or the processor failed |
| Who's the audience for the message | Your engineers | Your customer (sometimes), your support team (always) |
| Code spaces | One: ApiErrorCode | Three: PaymentErrorCode, DeclineCode, raw network code |
| Handler shape | try / catch, branch on error.type and error.code | Read lastPaymentError, branch on adviceCode, pick customer-safe copy |
| Reference | API error codes | Payment error codes, Decline codes |
Two things every error gives you
A request_id
Every API response (whether 2xx or error) includes a request_id like req_3STkwmFGhGHoO0IX13BRo5iU. On error responses it's at the top level alongside error. Quote it in support tickets, log it next to every error your handler catches, and use it to look up the failing call in Workbench → Logs where you can see the full request and response.
A failed payment also has a request_id on the underlying API call that triggered it (the POST /checkout/sessions/.../pay from the customer's browser). The lastPaymentError.chargeId plus the time of failure are usually enough to find that log if you need it.
A docUrl
Every error code has a deep link to its row on a code reference page in this docs site:
- API error codes →
https://docs.xpay.app/integrate/errors/api-error-codes#<code> - Payment error codes →
https://docs.xpay.app/integrate/errors/payment-error-codes#<code> - Decline codes →
https://docs.xpay.app/integrate/errors/decline-codes#<code>
The dashboard surfaces this link in transaction tooltips and in Workbench → Logs so you can click straight from a failed event to its explanation. In your own ops UI, render the docUrl as a "Learn more" link next to the error message.
Pick where to go next
API errors
The error envelope, the six error types, the handling pattern (try/catch + branch on type).
API error codes
The full list of error.code values you can receive on a failed API call, grouped by domain.
Payment errors
What lastPaymentError looks like, the adviceCode that decides what to do next, and how to split copy between your customer and your team.
Payment error codes
Every value of lastPaymentError.code, with the customer-safe and merchant-facing copy XPay returns.
Decline codes
The card issuer's reason for the decline, in lastPaymentError.declineCode. Plus the raw network code that came back from the card brand.
Logs panel
Look up any failure by its request_id and see the full request and response.