Understanding declines and rejections
How to read a refused payment: the three result fields, the refusal families, which ones leave a hold, what you can retry, and what to tell the payer.
A refused payment is a transaction result, not an error. When the issuer, the processor, or one of the merchant's own rules says no, the create call still succeeds: it answers HTTP 200 with the transaction, and the refusal is written on that transaction in three fields. The one exception is a screening stop, which refuses the request before a transaction result exists and so arrives as an HTTP error instead.
This guide is for the integrator reading that response. It covers the three fields to read and how they relate, the three families of refusal and the screening stop, which refusals leave a hold on the cardholder's funds and what releases it, which outcomes are worth retrying and which must never be retried unchanged, the webhook sequence each family produces, what to show the payer, and the one refusal that arrives days later: an Automated Clearing House (ACH) return.
If you're looking up a specific declineReasonCode value, the decline reason code reference lists every value with its meaning and whether a hold was placed. This guide explains how to read the response those values sit in.
The three fields to read
Every refusal that reaches a transaction result is described by the same three fields. Read them in this order.
| Field | What it carries | Read it for |
|---|---|---|
resultCode |
What happened to the request, as one value from a fixed set. Ok is an approval. Decline is the common refusal. PolicyRejected is a refusal by one of the merchant's own rules. The same value sits at responseData.resultCode. |
Which branch of your code runs: approved, refused, or failed. |
responseData.declineReasonCode |
Why it was refused, as one value from WinkPG's own vocabulary, the same whichever processor handled the payment: DO_NOT_HONOR, INSUFFICIENT_FUNDS, or CV_REJECTED, for example. Present on a refusal, absent on an approval. |
Whether the refusal is worth retrying, and what to record against the order. |
responseData.declineReasonDescription |
A readable sentence that goes with the code, in the processor's or WinkPG's own words. Wording can change without notice. | Your logs and your support tooling. Never branch on it. |
The relationship between them is a funnel. resultCode says that the payment was refused and by which kind of decision maker. declineReasonCode says why, in a stable vocabulary you can switch on. declineReasonDescription says the same thing in words a person can read. Branch on the first two and display the third.
Two more fields complete the picture:
responseData.resultMessageis the customer-safe result text. On a policy rejection it's generic on purpose, for a reason covered under What to show the payer.responseData.processorResponseCodeis the processor's own native code. It's useful when you're on the phone with the processor and for nothing else: it means something different on every processor, anddeclineReasonCodeis the translation.
resultCode takes a handful of other refusal values beyond Decline and PolicyRejected, such as Referral, Reject, and InsufficientFundsAvailable, depending on how the processor phrased its answer. WinkPG treats every one of them as a decline: the transaction's lifecycle status is Declined and the webhook is Transaction.Declined. Treat anything other than Ok and Partial as not approved, then read declineReasonCode for the reason.
The refusal families at a glance
| Family | Who decided | resultCode |
declineReasonCode |
Hold on the cardholder | Retry the same request |
|---|---|---|---|---|---|
| Processor decline | The issuer or the processor, at authorization | Decline (or a more specific refusal value) |
A value from the reference, such as DO_NOT_HONOR |
None. The processor didn't approve. | Only for the funding and availability codes listed under What you can retry |
| Gateway policy rejection | The merchant's address verification (AVS) or security code (CVV) rule | PolicyRejected |
CV_REJECTED or CV_NATIVE_DECLINE |
CV_REJECTED: placed, then released by an automatic reversal. CV_NATIVE_DECLINE: none. |
Never unchanged. The rule fires again. |
| Review decline | A reviewer, or the review deadline | PolicyRejected |
REVIEW_DECLINED or REVIEW_EXPIRED |
None. The hold parks the transaction before authorization. | Never as a retry of this transaction. A new attempt is a new transaction. |
| Screening stop | One of the merchant's screening rules, before authorization | None. The response is an HTTP 403 error, not a transaction result. |
None. The error code is SCREENING_STOPPED. |
None. Nothing reached a processor. | Never unchanged. The rule fires again. |
The sections that follow show a sample response for each.
Processor declines
The issuer or the processor refused the authorization. This is the family most declines belong to, and the only one whose reason is decided outside WinkPG. The processor's native code is translated into declineReasonCode so your code reads one vocabulary whichever processor the merchant is routed to.
A processor decline places no hold. The processor didn't approve, so there's nothing on the cardholder's funds and nothing to release.
{
"id": "6f3b2c18-0a4d-4a9e-9d4f-2b71c2f0a911",
"transactionType": "Sale",
"currentStage": "Declined",
"resultCode": "Decline",
"invoiceData": {
"amounts": { "base": 50.00, "total": 50.00 }
},
"responseData": {
"resultCode": "Decline",
"resultMessage": "Declined",
"declineReasonCode": "INSUFFICIENT_FUNDS",
"declineReasonDescription": "Insufficient Funds",
"processorResponseCode": "51"
}
}
processorResponseCode is shown for completeness: the value and its meaning vary by processor, and 51 is only what one processor happens to say. Branch on INSUFFICIENT_FUNDS.
To exercise this branch before you go live, use the amounts your sandbox refuses on purpose. The testing page on the developer documentation site lists them, and reads them from the simulator rather than restating them, so they can't drift.
Gateway policy rejections
The processor answered, and then one of the merchant's own card verification rules refused the payment. The merchant's address verification and security code settings decide which response codes the merchant accepts, and a code outside that list rejects the transaction even when the issuer approved it. Where the rules live and how they're configured is covered in the card verification section of Transaction screening and fraud rules.
resultCode is PolicyRejected (2100) for this whole family, and declineReasonCode says which of two shapes it took. The difference is whether a hold ever existed.
CV_REJECTED. The processor approved, so a hold was placed on the cardholder's funds, and WinkPG then rejected the approval under the merchant's rule. WinkPG releases the hold for you with an automatic reversal or void, and the transaction ends at theReversedstage once that reversal is approved. A zero-amount verification places no hold, so it has nothing to reverse and ends atPolicyRejected.CV_NATIVE_DECLINE. The processor applied the merchant's rule itself and declined outright, before any approval. No hold was placed, nothing is reversed, and the transaction ends atPolicyRejectedwith the reversal fields empty.
Both shapes carry a policyRejection block naming the rule that fired.
{
"id": "0b7d1e52-6c3a-4f8e-9a21-5d4c8e2f7b30",
"transactionType": "Sale",
"currentStage": "Reversed",
"resultCode": "PolicyRejected",
"invoiceData": {
"amounts": { "base": 50.00, "total": 50.00 }
},
"responseData": {
"resultCode": "PolicyRejected",
"resultMessage": "Transaction declined",
"declineReasonCode": "CV_REJECTED",
"declineReasonDescription": "Transaction declined"
},
"policyRejection": {
"deniedCode": "N",
"deniedCodeType": "Avs",
"deniedCodeDescription": "AVNoMatch",
"ruleSource": "AvsDeclineCodes",
"rejectedAt": "2026-09-11T14:30:05Z",
"requiresReversal": true,
"reversalResultCode": "Ok",
"reversalResultMessage": "APPROVED",
"reversalAttemptedAt": "2026-09-11T14:30:06Z"
}
}
policyRejection.deniedCode is the processor's address verification or security code response code, such as N for no match. It's a processor result, never anything the cardholder typed. deniedCodeType is Avs or Cvv, deniedCodeDescription is the accept-list entry that failed, and ruleSource is the list it belongs to.
requiresReversal and the three reversal* fields are what you read to confirm the hold is gone. The reversal is a processor call and the processor can refuse it, so a transaction that stays at PolicyRejected with requiresReversal: true and a failed or missing reversalResultCode still carries the hold. That case needs a reversal or a void from you; Refunds, voids, and reversals covers which one the transaction accepts.
Review declines
Some merchants send transactions to an external screening provider, and a provider that answers review on a merchant configured to hold for review parks the transaction before authorization until a person decides. This family is different from the other two in one way that matters to your code: the refusal never arrives on the create response. The create call is answered while the transaction is on hold, and the decline lands on the transaction later.
While the hold is open, the transaction you read back carries a fraudReviewData block with disposition Pending, the deadline in deadlineUtc, and the provider that triggered the hold. No resultCode refusal is present, because nothing has been decided. If you subscribe to webhooks, Transaction.ReviewHeld tells you the hold was placed without polling.
Release or reject a held transaction over the API
A hold is decided by a person, and that person doesn't have to be in the back office. The same decision the review queue offers is published on the API, so an integration with no back-office access can clear its own holds:
POST /api/transactions/{transactionId}/fraud-review/approve
Content-Type: application/json
{
"reasonCode": "VERIFIED_BY_PHONE"
}
POST /api/transactions/{transactionId}/fraud-review/decline
Content-Type: application/json
{
"reasonCode": "SHIPPING_ADDRESS_UNVERIFIABLE"
}
Both calls need the transactions:write scope and the Approve or Decline Held Transactions permission on the key's owning user; a merchant-scoped key can decide its own merchant's holds. reasonCode is optional on an approval and required on a decline: a decline is an adverse decision the merchant has to be able to substantiate later. It's recorded on the transaction as the merchant's audit trail, so send a short code or note of your own. Never relay a screening provider's or processor's response text in it, and never anything card-derived.
Both calls answer with the transaction as it stands after the decision was applied, so read fraudReviewData.disposition off the response rather than off what you sent. An approval releases the transaction into authorization; the processor's answer then lands on the transaction the same way it does for a payment that was never held, and Transaction.Approved or Transaction.Declined follows. A decline ends the transaction as REVIEW_DECLINED, described below.
Three refusals are specific to this pair:
| Status | Meaning | What to do |
|---|---|---|
404 |
No held transaction with that id is visible to this key. A transaction that belongs to another merchant answers the same way. | Check the id and the key's merchant. |
409 |
The transaction isn't awaiting a decision: it was already approved or declined, or its deadline passed. | Read the transaction. The outcome is already on it. |
429 |
The decision was recorded, but the updated transaction couldn't be read back in time. | Read the transaction. Don't submit the decision again; it's already durable. |
The hold resolves one of two ways for your integration.
REVIEW_DECLINED. A reviewer looked at the order and rejected it.REVIEW_EXPIRED. Nobody dispositioned the hold before its deadline, and the merchant's expiry action is to decline. The same code arrives when the backstop sweep reclaims a hold whose review never completed, because from your side that's exactly what happened.
Either way resultCode is PolicyRejected, the transaction's stage is Rejected, and no hold on the cardholder's funds ever existed: the park happens before authorization.
{
"id": "3e9a4b71-2f5c-4d06-8b1e-7c2d9f0a6e44",
"transactionType": "Sale",
"currentStage": "Rejected",
"resultCode": "PolicyRejected",
"invoiceData": {
"amounts": { "base": 50.00, "total": 50.00 }
},
"responseData": {
"resultCode": "PolicyRejected",
"resultMessage": "The transaction was declined by manual review.",
"declineReasonCode": "REVIEW_DECLINED"
},
"fraudReviewData": {
"disposition": "Declined",
"heldAtUtc": "2026-09-11T14:30:05Z",
"deadlineUtc": "2026-09-12T14:30:05Z",
"dispositionAtUtc": "2026-09-11T16:02:41Z"
}
}
Read the two codes differently. REVIEW_DECLINED means a person rejected this order; REVIEW_EXPIRED means nobody looked at it in time. Neither is something a retry of the same transaction can change: the hold is gone, so a fresh attempt is a new transaction, and it will be screened again. A merchant who wants unattended holds to approve rather than decline changes the expiry action on their screening profile, which is configuration and not something the caller can influence.
Screening stops
The screening rules run before WinkPG contacts a processor, and a rule set to stop refuses the transaction before a result exists. That's why this is the one refusal that isn't a transaction result: there's no authorization to describe, so the create call answers with an error instead.
{
"error": {
"code": "SCREENING_STOPPED",
"message": "Transaction stopped by screening contributor."
}
}
It arrives as HTTP 403. The transaction record exists and keeps the screening verdict on it, so an operator can see which rule fired, but it carries no resultCode and no declineReasonCode, and no Transaction.Declined webhook is published for it. A screening stop is final: the identical request is stopped by the same rule again. The rules themselves, what each one flags, and the one request-level override are in Transaction screening and fraud rules.
A related code, SCREENING_UNAVAILABLE, means an external screening provider gave no answer and the merchant's profile is configured to stop rather than continue. Unlike SCREENING_STOPPED, it's transient: a later retry can succeed with no change to the request.
Failures versus declines
One more outcome looks like a refusal and isn't. A failure means WinkPG couldn't complete the request at all: a processor timed out, a host was unreachable, or the request couldn't be processed as submitted. The payment was never decided, no hold was placed, and the webhook is Transaction.Failed rather than Transaction.Declined.
A failure's resultCode names the fault (GeneralError, TimeoutWaitingForProcessorResponse, ProcessorNotConfigured, for example) and it usually carries no declineReasonCode, because there's no decline to classify. Don't route a failure through your decline handling. The productive next step once the fault clears is a new create with a new idempotency key. The Retry operation on the transaction is seldom offered here: it's withdrawn from a transaction that carries no decline reason code, which a failure usually doesn't, so read allowedActions rather than assuming it. Don't resend the same key: a create that answered, even with a failure, is replayed under that key for 48 hours, so the resend hands you the same failed transaction back. Reusing a key is for the other case, a request that got no answer at all, and Getting started with the API covers how it protects you from charging twice there.
Two gateway-side refusals sit on the boundary and carry a reason code so you can tell them apart from a processor's answer. CB_OPEN means recent calls to the routed processor were failing and its connection is temporarily suspended, so nothing was sent; retry once the processor recovers. WALLET_CRYPTOGRAM_UNSUPPORTED means a wallet payment supplied authentication data the routed processor has no confirmed field for, so the authorization was refused before it was sent; it needs the mapping confirmed with the processor before a retry can succeed.
Which refusals leave a hold, and what releases it
An authorization hold is the issuer reserving the cardholder's funds after an approval. It's the question behind most decline support calls: "was the customer's money held?" The answer depends on who decided the refusal, and every value in the decline reason code reference states it.
| Who decided | Examples | Hold placed | Released by |
|---|---|---|---|
| The issuer or the processor, at authorization | DO_NOT_HONOR, INSUFFICIENT_FUNDS, EXPIRED_CARD, CARD_VERIFICATION_FAILED, CV_NATIVE_DECLINE |
No. The processor didn't approve. | Nothing to release. |
| WinkPG, after the processor approved | CV_REJECTED |
Yes. | An automatic reversal or void, issued by WinkPG. Confirm it in policyRejection.reversalResultCode. |
| WinkPG, before contacting the processor | CB_OPEN, WALLET_CRYPTOGRAM_UNSUPPORTED, REVIEW_DECLINED, REVIEW_EXPIRED, a screening stop |
No. Nothing was sent. | Nothing to release. |
| The receiving bank, on an ACH return | UNAUTHORIZED_DEBIT, PAYMENT_STOPPED |
No hold in the card sense. The funds movement itself was reversed. | Not applicable. See ACH returns arrive later. |
CV_REJECTED is the only refusal where a hold was placed and WinkPG released it for you. In the ordinary case there's nothing for you to do afterward; the one thing to check is the reversal outcome, as described under Gateway policy rejections.
What you can retry
WinkPG classifies every declineReasonCode as retryable or terminal, and the classification is conservative on purpose. A retryable decline is a funding or availability condition that a later attempt with the same payment method can plausibly clear without the cardholder doing anything. Everything else is terminal: re-presenting the same card gets the same answer, and a card the issuer has flagged as lost or stolen shouldn't be re-presented at all.
| Disposition | declineReasonCode values |
Why |
|---|---|---|
| Retryable | INSUFFICIENT_FUNDS, ACTIVITY_LIMIT_EXCEEDED, EXCEEDS_APPROVAL_AMOUNT |
Funding and limit conditions. The balance or the daily limit resets. |
| Retryable | ISSUER_UNAVAILABLE, SYSTEM_MALFUNCTION, CB_OPEN |
Availability faults. The issuer, the network, or the processor connection recovers. |
| Terminal | Every other value, including DECLINED, DO_NOT_HONOR, EXPIRED_CARD, LOST_CARD, STOLEN_CARD, SUSPECTED_FRAUD, CARD_VERIFICATION_FAILED, and INVALID_CARD_NUMBER |
The condition doesn't clear by re-presenting the same credential. |
| Terminal | CV_REJECTED, CV_NATIVE_DECLINE, REVIEW_DECLINED, REVIEW_EXPIRED |
The merchant's own rule or reviewer refused it. The same request gets the same answer. |
| Terminal | Absent or unrecognized | A decline WinkPG can't classify is treated as terminal. Stopping is the safe posture for a retry gate. |
This classification is what WinkPG's own automatic invoice payments use: a retryable decline is scheduled again on the configured retry schedule, and a terminal one stops the schedule and surfaces the invoice for someone to act on. Follow the same rule in your own retry logic.
Three things are outside the classification entirely:
- A screening stop and a policy rejection are never retryable unchanged. The rule that refused the request is deterministic. Retrying the identical request re-runs the rule and gets the same answer, and a burst of identical retries is exactly the pattern the velocity rule exists to catch.
Retryon the transaction is narrower than the classification. The card networks only permit resubmitting a decline tied to the cardholder's funds or limits, so theRetryoperation on a declined transaction is offered forINSUFFICIENT_FUNDS,ACTIVITY_LIMIT_EXCEEDED, andEXCEEDS_APPROVAL_AMOUNTand withdrawn for every other decline, including the availability faults above. ReadallowedActionson the transaction rather than assuming. Where a refused payment needs re-running outside that set, the path is a fresh charge under a stored-credential consent, not a resubmission of the declined one.- Retryable doesn't mean retry now. An issuer that reports insufficient funds at 14:30 reports it again at 14:31. Space retries by days, cap the number of attempts, and stop when the classification says terminal.
The webhook sequence per family
Every refusal that reaches a transaction result publishes Transaction.Declined, whatever resultCode it carries; a policy rejection resolves to the lifecycle status Declined like any other refusal. What differs is what arrives before and after it.
| Family | Sequence | Notes |
|---|---|---|
| Processor decline | Transaction.Declined |
One event. DeclineReasonCode on the payload carries the same value as the transaction. |
Policy rejection, CV_REJECTED |
Transaction.Declined, then Transaction.Reversed |
The reversal closes after the rejection is recorded. A Transaction.Reversed that never follows a Transaction.Declined carrying requiresReversal: true on the transaction is the signal to read the transaction back and check policyRejection.reversalResultCode. |
Policy rejection, CV_NATIVE_DECLINE |
Transaction.Declined |
No reversal was needed, so nothing follows. The same applies to a zero-amount verification rejected under CV_REJECTED. |
| Review decline | Transaction.ReviewHeld, then Transaction.ReviewDeclined, then Transaction.Declined |
ReviewHeld arrives when the hold is placed, possibly hours before the other two. ReviewDeclined and Declined describe one refusal; subscribing to both gets you two events for it, by design, and ReviewDeclined is delivered first so the order you read matches cause and effect. ReviewExpired on the ReviewDeclined payload is true when the deadline decided it. |
| Screening stop | None | No lifecycle event is published. The refusal is on the create response, and the transaction record keeps the verdict. |
| Failure | Transaction.Failed |
Not a decline. See Failures versus declines. |
Webhook delivery is at-least-once and events for one transaction can arrive out of order under retry, so key your handling on the transaction's current state rather than on the event alone. Webhook integration covers the envelope, the signature, and deduplication.
What to show the payer
The payer needs to know the payment didn't go through and what to do next. They don't need, and must not be given, the reason the merchant's rule fired.
- For a processor decline, tell the payer their bank declined the payment and to try another payment method or contact their bank. The issuer gives no reason to relay for the most common decline,
DO_NOT_HONOR, and the specific ones (INSUFFICIENT_FUNDS,EXPIRED_CARD) are safe to state in plain words if you choose to. - For a policy rejection, show
responseData.resultMessage, which readsTransaction declined, and nothing more specific. The generic wording is deliberate. The code inpolicyRejection.deniedCodetells whoever is holding the card exactly which check failed, an address mismatch or a wrong security code, and that's the feedback a card tester is probing for. Never relay the AVS or CVV result to the payer, in the message, in an error field, or in a distinguishable response time. - For a review decline, the payer sees a refused payment. Which reviewer decided it, and why, is the merchant's business.
- For a screening stop, present it as a refusal, not as a transient error the payer should retry.
[!IMPORTANT] Relaying
policyRejection.deniedCode,deniedCodeType, ordeniedCodeDescriptionto the payer turns your checkout into a verification oracle: it confirms which of the address and the security code was right, one attempt at a time. Keep those fields in your own records and in your merchant-facing tooling only.
declineReasonDescription is written for the merchant, not the payer. It's the right text for your order notes and your support screens, and the wrong text for a checkout page.
ACH returns arrive later
An ACH debit has no authorization step, so it's never declined in the moment. The receiving bank accepts the entry and can return it afterward, often days later and sometimes after the payment has already reported as settled. The return is the ACH equivalent of a decline, and it uses the same vocabulary.
A returned ACH transaction carries the bank's standard NACHA return code in responseData.nachaReturnCode and its reason in responseData.nachaReturnReason, and WinkPG translates the return into declineReasonCode so the same code that handles a card decline can handle it. Four values exist only for ACH, because the card vocabulary has no honest word for them:
declineReasonCode |
NACHA codes | Meaning |
|---|---|---|
UNAUTHORIZED_DEBIT |
R05, R10, R11, R29 |
The account holder, or their bank on their behalf, says the debit wasn't authorized as taken. |
AUTHORIZATION_REVOKED |
R07 |
The account holder withdrew the authorization the debit was taken under. Re-presenting is improper, not merely futile. |
PAYMENT_STOPPED |
R08 |
The account holder stopped this particular debit. The account itself is fine. |
ACCOUNT_FROZEN |
R16 |
The account is frozen and the bank may not post to it. |
Returns the card vocabulary already covers reuse it: R01 and R09 are INSUFFICIENT_FUNDS, R02 is CLOSED_ACCOUNT, and an account the entry can't reach (R03, R04, R12, R20) is INVALID_ACCOUNT. A return code the vocabulary doesn't recognize classifies as DECLINED, with the raw code still on nachaReturnCode for reconciling against your bank statement.
The webhook is Transaction.Returned rather than Transaction.Declined, and its payload carries the return code, the reason, and IsLateReturn so you can tell a return that arrived after settlement from one that arrived before. On a late return the transaction moves to the SettlementRolledBack settlement status. The practical consequence is worth stating plainly: treat an ACH settlement as durable rather than final when you decide to fulfil an order, and weigh how long you wait against the value of the order. ACH payments covers the bank-debit lifecycle in full.
Related guides
- Decline reason code reference for every
declineReasonCodevalue, its meaning, and whether a hold was placed. - Transaction screening and fraud rules for the rules that produce a screening stop, and the card verification settings behind a policy rejection.
- Transaction lifecycle and settlement for the stages a transaction moves through and what an operator sees on a declined, failed, or policy-rejected payment.
- Refunds, voids, and reversals for releasing a hold yourself when an automatic reversal was refused, and for what
Retryon a transaction accepts. - ACH payments for how an ACH return reaches you and how its status advances.
- Webhook integration for receiving the events in this guide at your own endpoint.