Two different things can come back from a payment that did not go through, and they need opposite handling.
- An error means the request failed. You get a
4xxor5xxand an error body. No transaction was created. - A decline means the request succeeded and the issuer said no. You get a
200and a transaction whoseresultisDECLINED.
This page covers both, errors first.
Every error, from every endpoint, has the same four fields.
{
"error_code": "INVALID_REQUEST_DATA",
"error_message": "The API request contains invalid data.",
"details": {
"amount": "Invalid amount",
"payment_method.pan": "Malformed PAN"
},
"source": {
"system": "TSYS",
"code": "13",
"message": "Invalid amount"
}
}| Field | Purpose |
|---|---|
error_code | The stable code. Branch on this |
error_message | The same thing in words, for a log. May be reworded; do not match on it |
details | Field-level specifics, as a flat map of field name to problem. May be null |
source | The processor's own system, code, and message, passed through unchanged. May be null |
Two rules govern it:
- Specifics live in
details, never inerror_message. That keeps the code list small and the messages stable. sourceis the processor, not us.systemisTSYSorVERICHECK. It is there for your support and reconciliation, not for your control flow — you branch onerror_code.
Thirteen codes can reach you today.
| HTTP | error_code | What happened | Retry |
|---|---|---|---|
| 400 | INVALID_REQUEST_DATA | A field is present and its value is unusable | After fixing the value |
| 400 | REQUIRED_DATA_MISSING | A required field is absent | After supplying it |
| 400 | INVALID_ACTION | The request is well formed, but the operation does not apply to this transaction's state | No |
| 400 | AMOUNT_LIMIT_EXCEEDED | A single amount is outside the allowed range | No |
| 400 | VELOCITY_LIMIT_EXCEEDED | A count or value cap over a period is spent | Later |
INVALID_ACTION is the one to read carefully. It does not mean the request was malformed; it means the thing you asked for does not make sense for that transaction right now — capturing a sale, refunding something unsettled, canceling something already canceled. The section below lists the common ones.
| HTTP | error_code | What happened | Retry |
|---|---|---|---|
| 401 | NOT_AUTHENTICATED | Api-Key or Client-Id is missing or wrong | No |
| 403 | UNAUTHORIZED_ACTION | Authenticated, but not permitted to do this | No |
| 404 | RESOURCE_NOT_FOUND | No such URL, or no such resource ID | No |
| 409 | DUPLICATE_ACTION | The gateway or the processor has already seen this | No |
A 401 does not distinguish a missing header from a wrong key. Check that both Api-Key and Client-Id are present before assuming the key is bad — see Authentication.
| HTTP | error_code | What happened | Who fixes it | Retry |
|---|---|---|---|---|
| 500 | SYSTEM_ERROR | An unhandled fault inside the gateway | Us | Yes |
| 500 | INTEGRATION_CONFIG_ERROR | The merchant's stored integration record is wrong, such as a bad MID | Us, with the merchant's boarding data | Not until it is corrected |
| 502 | DOWNSTREAM_ERROR | The gateway reached the processor and could not use the reply | Us | Yes, but reconcile first |
| 503 | PROCESSOR_UNAVAILABLE | The processor is down or unreachable, and did not process the request | The processor | Yes, with backoff |
500 and 502 are not interchangeable. A 500 means the processor was never reached, so no transaction exists. A 502 means it was reached and may have seen the transaction. A 503 means the processor did not process the request, so no transaction exists, and a retry after a backoff is safe. There is no idempotency key, so retrying a payment after a 502 without reconciling first can charge twice. See Errors and retries.
The description declares eight of these thirteen on its operations: INVALID_REQUEST_DATA, NOT_AUTHENTICATED, UNAUTHORIZED_ACTION, RESOURCE_NOT_FOUND, DUPLICATE_ACTION, SYSTEM_ERROR, DOWNSTREAM_ERROR and PROCESSOR_UNAVAILABLE. The last is named in the description of the 503 response, which carries no example yet.
The other five — INVALID_ACTION, REQUIRED_DATA_MISSING, AMOUNT_LIMIT_EXCEEDED, VELOCITY_LIMIT_EXCEEDED and INTEGRATION_CONFIG_ERROR — arrive from the processor-mapping layer and are not in the description yet.
So a generated SDK's error enum is not the whole list. Handle an unrecognized error_code by falling back on the HTTP status rather than throwing.
All of these return 400 with INVALID_ACTION. The code stays the same and the message narrows.
Capture
- Transaction already captured
- The transaction is not in an authorized state
- Cannot capture a
SALE - Cannot capture an ACH transaction
- Cannot capture a declined transaction
Cancel
- Transaction already canceled
- Cannot cancel a declined transaction
- Cannot cancel a transaction that has already settled
Refund
- Cannot refund a transaction that has not settled
- Already refunded in full
- The amount exceeds the remaining refundable amount
- The amount exceeds the original transaction amount
- Cannot refund a canceled transaction
Batch close
- A batch close is already in progress
- No open batch available to close
Payment method and transaction type
- Both card data and a payment token were sent
- Both ACH data and a payment token were sent
AUTHis not supported for ACHPAYOUTis not supported for cardsCAPTURE,CANCELandREFUNDare not valid types for creating a transaction
A declined payment is a successful API call. It returns 200 with a transaction whose result is DECLINED. No error_code is set, because nothing went wrong with the request.
{
"result": "DECLINED",
"response_code": "51",
"response_description": "Insufficient funds",
"batch_id": null
}Three tests separate the two:
- Did the processor make a decision? A decision, even a negative one, is a decline. A failure to reach a decision is an error.
- Would resending the identical request help? If no, because the issuer refused, it is a decline.
- Is there a field to fix or a setting to change? If yes, it is an error. If the request was valid and the money simply was not authorized, it is a decline.
Treating a decline as an error is the most expensive mistake on this page. A client written against a 4xx will retry it, alert on it, or show the cardholder a system fault — and none of those is right.
Handle declines covers what to do with each outcome.
source.code and source.message pass through unchanged, so you see exactly what TSYS or VeriCheck said.
| Processor | Code shape | Example |
|---|---|---|
| TSYS | Two characters | 13 invalid amount, 51 insufficient funds |
| VeriCheck | TN and three digits | TN007 routing number not provided |
These are for humans. Quote them in a support ticket alongside the x-request-id header; do not branch on them, because the mapping between processor codes and our codes can change without our contract changing.
Never show a processor code or message to a cardholder. "Do not honor" and 51 mean nothing to them, and some imply things about their account that are not yours to relay.