Skip to content

Handle declines

A declined payment returns 200. The request was well formed, it reached the issuer, and the issuer said no. Nothing is wrong with your integration.

That is the distinction your code has to make:

HTTP statusWhat it meansWhat to do
Approved200, result is APPROVEDThe issuer approved itFulfill
Declined200, result is DECLINEDThe issuer refused itAsk for another payment method
Error200, result is ERRORThe transaction could not be attemptedInvestigate; do not retry blindly
Failed request4xx or 5xxThe request never became a transactionSee Errors and retries

Branch on result, not on the status code. Treating any 200 as success is the most common defect in a first integration, and it ships goods for payments that never happened.

What a decline looks like

{
  "id": "TRANSACTION-01KEW36C9TQ5N1P7YRFGZ0BVW4",
  "type": "SALE",
  "result": "DECLINED",
  "response_code": "51",
  "response_description": "Insufficient funds",
  "batch_id": null,
  "amount": 10000
}

response_code and response_description come from the processor. batch_id is null, because a declined transaction never joins a batch.

The transaction still exists and still has an id. A decline is recorded, not discarded, so you can read it back later from the reporting endpoints.

Retry, or do not

The useful split is between declines that might succeed later and declines that never will.

  • Do not retry the same card immediately. Card networks penalize repeated attempts on a refused card, and issuers may treat a burst as suspicious.
  • A soft decline — insufficient funds, a velocity limit — may succeed on a different day. For a recurring charge, retry on a schedule measured in days, not seconds.
  • A hard decline — closed account, stolen card, invalid number — will not succeed. Ask for a different payment method.
  • Never retry with a slightly different amount to get around a decline. It looks like probing, and it is.

The gateway does not classify a decline as soft or hard for you. The processor's response_code carries that meaning, and the mapping is processor-specific.

What to tell the customer

Do not show response_code, response_description, or anything under source. "Do not honor" and 51 mean nothing to a cardholder, and some of them imply things about their account that you should not be relaying on the issuer's behalf.

Say what they can do instead:

That card was declined. Try another card, or contact your bank.

Keep the codes in your logs, where they are worth having. Log the x-request-id header with them — it is what support needs to trace one specific attempt.

ACH is different

An ACH debit can be approved and then returned days later. There is no decline at the moment of payment to catch.

Watch is_returned on the transaction rather than waiting for a decline that will not come. Accept an ACH payment covers the return fields and the trap that return_code is omitted rather than null.

Testing a decline

No card number triggers a decline against the real gateway. To exercise this path in the API reference, send the header x-redocly-response-body-example: saleDeclined and the mock returns a declined body. See Environments and test data.

Next steps