Skip to content
Last updated

Errors and retries

Two different things can go wrong with a payment, and they look nothing alike.

  • The request failed. The API returns a 4xx or 5xx with an error body. Nothing was charged.
  • The payment was declined. The API returns 200 with a transaction whose result is DECLINED. The request worked; the issuer said no.

This page is about the first. For the second, see Handle declines.

The error envelope

Every failure returns 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"
  }
}
FieldWhat it carries
error_codeThe stable code to branch on. Always present
error_messageThat code in words, for a log
detailsField name to problem, when the failure was about specific fields. Otherwise null
sourceThe processor's own code and text, passed through unchanged. Null fields when the gateway failed before reaching a processor

Branch on error_code, never on error_message. The message is prose and may be reworded. The code is part of the contract.

source is for your support and reconciliation, not for your control flow. It tells you what TSYS or VeriCheck said, in their vocabulary, so a support ticket can quote it.

The codes

Seven, and that is the whole list.

Statuserror_codeWhat happenedSafe to retry
400INVALID_REQUEST_DATAThe request is malformed or a field is invalid. details names the fieldsNo — fix the request
401NOT_AUTHENTICATEDApi-Key or Client-Id is missing or wrongNo — check both headers
403UNAUTHORIZED_ACTIONAuthenticated, but not allowed to do thisNo
404RESOURCE_NOT_FOUNDNo such transaction, client, or integrationNo — check the ID
409DUPLICATE_ACTIONThe action conflicts with the resource's current stateNo — read the resource first
500SYSTEM_ERRORA fault inside the gatewayYes, with backoff
502DOWNSTREAM_ERRORThe gateway reached the processor and could not use its replyYes, but read the next section first

A 401 does not distinguish a missing header from a wrong key. If you get one, check that both Api-Key and Client-Id are present before assuming the key is bad — a missing Client-Id produces the same answer.

Not every operation can return every code. 404 is declared on 11 of the 19 operations, 409 on 7, and 502 on only 4: creating, capturing, and canceling a transaction, and closing a batch.

500 and 502 are not the same

This is the distinction that matters when money is involved.

  • A 500 is a fault inside the gateway. The processor was never reached, so no transaction exists.
  • A 502 means the gateway reached the processor and could not use what came back. The processor may have seen the transaction.

Both are safe to retry in the sense that the API will accept the request again. Only one is safe to retry blindly.

After a 502 on a payment, reconcile before retrying. Call GET /v1/transactions filtered to the window in question and look for a transaction matching what you sent. The API has no idempotency key, so a retry that the first attempt already completed creates a second charge.

Retrying

Retry 500 and 502. Do not retry 400, 401, 403, 404, or 409 — the request will fail the same way until you change it.

Use exponential backoff with jitter: wait a short interval, double it each attempt, and add a small random offset so a fleet of clients does not retry in lockstep. Three or four attempts is usually enough; past that, the failure is not transient.

There is no rate limit response today. If you are sending enough traffic to worry about one, talk to your implementation contact rather than designing around a 429 the API does not return.

Every response carries a request ID

Successful or not, every response includes an x-request-id header holding a GUID.

x-request-id: 73039bd9-c380-4186-bffe-259125144a56

Log it with every request you send. It is what lets support trace one specific call through the gateway. A support ticket without it usually turns into a request for it.

What to show a person

Do not put error_code, response_code, or anything from source in front of a cardholder. "Do not honor" and 51 mean nothing to them and sometimes imply something untrue about their account.

Map to a short, actionable sentence — "That card was declined. Try another card" — and keep the codes in your logs, where they are useful.

Next steps