How to investigate a failed payment: a playbook for support teams
To investigate a failed payment, work in order. Find the payment by its reference, list every attempt, read the provider response on each attempt, check the 3-D Secure result, confirm the webhook reached your systems, then compare all that with what the customer saw. Most cases come down to an issuer decline, a blocked payment, failed or abandoned authentication, or a webhook that never arrived.
In 30 seconds
- Investigate in a fixed order: reference, attempts, provider response, 3-D Secure, webhook, customer view. Skipping steps leads to wrong answers.
- A failed payment is not always a decline. It may have been blocked, abandoned or never sent, and each needs a different reply.
- Trust the final server-side status, not the browser. Customers can leave the page, and result codes can change after they arrive.
- 12 sources cited
On this page8 sections
A customer writes in: the payment failed, the money seems to have gone, and the order isn’t there. Support opens one dashboard, sees “declined”, tells the customer to call the bank, and the case comes back a day later. Often the answer was on the screen, one step further down.
This article is a playbook for that one payment. It sets out what to check, in order: the reference, the attempts, the provider response, the 3-D Secure result, the webhook and the customer’s view. It ends with a table of symptoms and likely causes. For trends across thousands of payments, use a decline dashboard instead; this article starts where that one stops, with a single case.
What do you need before you start?
Collect the facts first, so you search once and search well. You need:
- Order or payment reference, from the confirmation email or the customer’s account.
- Customer email and account ID.
- Amount, currency and method. For cards, the last four digits only.
- Date and time, with the customer’s time zone.
- What they saw, in their own words or as a screenshot.
- What the bank shows: pending, completed or nothing.
Never ask for a full card number or CVV. You don’t need either, and they should not appear in a ticket.
Then write down the question you are answering. “Why did it fail?” is different from “Did I pay?” and from “Where is my money?”. The steps below answer all three, but the reply to the customer depends on which one they asked.
How do you trace the payment and its attempts?
Step 1: find the payment. Search by your order reference first, then the provider’s payment ID. If you only have an email and amount, widen the time window for time zones. If nothing turns up, the customer may have stopped before a payment was created, or may have paid another merchant with a similar name.
Step 2: list every attempt. One payment can have many attempts. A customer may retry. A cascade may move the payment to another provider. Stripe’s model shows the structure: a PaymentIntent tracks the whole purchase, each try creates a Charge, and you need to list all charges to see the unsuccessful ones, because the object itself shows only the latest. The same logic applies at any provider.
For each attempt, note the provider, method, time, amount and result. Then read the pattern:
| Pattern | What it usually means |
|---|---|
| One attempt, declined | Issuer or provider refused it; go to the provider response |
| Several attempts, last one approved | The customer paid; a failed attempt may be what they saw |
| Several attempts, two approved | Possible duplicate; check captures and order records |
| Attempt with no final status | Still pending, timed out or abandoned |
| Attempts on different providers | Cascade or customer retry; read each response |
| No attempt at all | Failed before a provider saw it |
Note the final status separately, and use one you can trust. Stripe’s statuses show why: a payment waiting for authentication sits at requires_action, one that failed returns to requires_payment_method, and in its Dashboard all of these can appear as Incomplete. Adyen warns that its Pending and Received codes mean the final status isn’t available yet, and that you should not rely on the result code to update your order system, because the status can change after you receive it.
If the first attempt ended in a timeout, don’t assume it failed. Check the status before any retry. Stripe’s documentation explains that idempotency keys let you repeat a request safely after a connection error, without creating a second payment. Our article on webhooks and idempotency covers how that works.
How do you read the provider response?
Step 3: identify who said no. Stripe sorts failures into three types, and the split works across providers:
| Type | Who refused | How to recognise it | What the customer should do |
|---|---|---|---|
| Issuer decline | The card issuer | A decline code from the network | Fix details, use another card or contact the bank |
| Blocked payment | Your or your provider’s fraud rules | No issuer response; Stripe shows not_sent_to_network | Not a bank problem; your team decides |
| Invalid request | Your integration | An error, often no payment on record | Nothing; it is yours to fix |
This is the step that saves the most time. If your own rule blocked the payment, sending the customer to the bank wastes a day for both of you. Stripe says that when it blocks a payment it doesn’t obtain authorisation from the issuer, and the outcome object shows the reason. Adyen separates these too: refusal reason 20, FRAUD, means pre-authorisation risk checks reached a score of 100 or more, while an issuer’s suspected fraud arrives as a different refusal.
For an issuer decline, record three things:
- The raw network code, not only the provider’s label.
- The Mastercard merchant advice code, if there is one. Our guide to merchant advice codes explains what each one tells you.
- The provider’s own message, if it adds detail.
Expect little from generic codes. Stripe says issuers categorise most declines as generic, and discuss the specifics only with their cardholders. A do not honor tells you the issuer refused, and little else. Stripe lists the usual reasons as insufficient funds, incorrect card data and suspected fraud, and says that in the last case the customer must resolve it with their issuer. Our article on soft and hard declines covers which ones can be retried, and Visa’s rules forbid any reattempt after a Category 1 decline.
How do you check the 3-D Secure result?
Step 4: read the authentication outcome. If the card went through 3-D Secure, authentication has its own result, separate from authorisation. A payment can pass authentication and then be declined by the issuer, or fail authentication and never reach authorisation. Stripe’s flow shows both: after authentication it attempts the charge, and after a failure the payment returns to requires_payment_method, with no charge attempted.
The protocol reports a transaction status. Adyen’s reference lists the meanings:
| Status | Meaning | What to do next |
|---|---|---|
| Y | Authenticated | Read the authorisation response |
| N | Not authenticated; transaction denied | Customer retries or uses another card; contacts the bank if it repeats |
| U | Authentication could not be performed | Customer tries again later or uses another method |
| A | Attempted; proof of attempt | Read the authorisation response |
| C | Challenge required | Check whether the customer finished the challenge |
| R | Issuer rejected the authentication | Customer uses another card or contacts the bank |
A reason code usually comes with it. Adyen lists examples such as 01, card authentication failed; 11, suspected fraud; 13, cardholder not enrolled; and 14, transaction timed out at the ACS, the issuer’s authentication server. Stripe reports a result such as authenticated, failed or attempt_acknowledged, with a result_reason such as abandoned, canceled, rejected or protocol_error.
Three patterns come up often:
- Abandoned. The customer never finished, perhaps because the bank app didn’t open or they closed the page. Stripe shows an unfinished checkout at
requires_action. Stripe’s mobile SDKs need a timeout of at least 5 minutes, so a stalled payment can stay open that long. - Failed. A wrong code or a rejected challenge. Adyen’s refusal 11, 3D Not Authenticated, means authentication didn’t run or didn’t succeed.
- Authentication required. Adyen’s refusal 38 means the issuer refused an exemption and wants 3-D Secure, which is a reason to try again with authentication, not to tell the customer the card is bad. Our guide to 3-D Secure exemptions explains why this happens.
Adyen’s refusal 42 points to a problem at the network or issuer. If many customers of one bank hit it at once, escalate to your provider rather than handling each ticket alone.
How do you check the webhook?
Step 5: confirm your systems got the final status. A provider can show a paid payment while your shop shows nothing, because the webhook that should update the order never arrived or wasn’t processed. This is the classic cause of “I paid but have no order”.
Check four things:
- Was an event sent? Look at the delivery log. Stripe lists events as delivered, pending or failed, with the HTTP status of each attempt.
- Did delivery succeed? Stripe’s table of errors covers connection failures, redirects, 4xx, 5xx, TLS errors and timeouts. Adyen says a 401 usually means your server refused the connection because of invalid credentials.
- Is a retry still coming? Stripe retries for up to three days in live mode, and Adyen retries on a schedule from 30 seconds up to 8 hours, then from a queue for up to 30 days. The order may fix itself, but a customer who is waiting needs an answer now.
- Was it processed? A 200 response only means the event was received. Check your order system’s logs. Stripe doesn’t guarantee delivery order, and an endpoint can receive the same event twice, so a handler can skip or mishandle an event if it isn’t built for this.
If the provider has a final status that your order system lacks, fix the order from the provider’s record and resend the event. Stripe warns against fulfilling orders from the browser, because customers can leave the page after paying. The same logic applies to investigations: the redirect back to your site proves nothing, either way.
What did the customer actually see?
Step 6: compare your record with the customer’s view. Most confusion comes from the gap between them.
- Bank app shows a pending amount. This may be an authorisation hold, not a charge. Stripe says that even for a blocked payment, customers with some card types might see the issuer’s authorisation, and that the issuer typically removes it within a few days. Timing is up to the issuer, so don’t promise a date.
- Bank shows a completed charge. If your records agree, the payment succeeded and the problem is on the order side. If yours show nothing, ask for a statement screenshot and check the merchant name and amount. It may be another merchant.
- Customer saw an error, but a later attempt worked. They may believe they paid twice, or not at all. Show them the attempts in plain words.
- Screen said failed, but provider says approved. This points to a timeout or a dropped redirect, so go back to the webhook step.
- Customer’s bank wants to trace it. For a settled card transaction, the acquirer can supply the acquirer reference number, a 23-digit reference that Stripe says stays with the transaction across the network and that bank staff use to trace it.
Then write the reply. Say what happened, what they should do next and what you have done. Keep it specific: “Your bank declined the payment, and no money was taken. Please try another card, or contact your bank and quote the date and amount” beats “Your payment failed”. Don’t repeat a raw fraud code to the customer. Say the bank refused the payment and ask them to contact it.
Which symptom points to which cause?
Use this as a quick guide, then confirm it with the steps above.
| Symptom | Likely cause | First check | Who acts |
|---|---|---|---|
| “Card declined”, no detail | Generic issuer decline | Network code, advice code | Customer and bank |
| Declined straight away, no bank alert | Blocked by fraud rules | Block reason in the attempt | Your risk team |
| Payment form gives an error, no payment on record | Invalid request or integration error | Provider logs | Your developers |
| Stuck on the bank’s page | Abandoned or timed-out authentication | 3-D Secure result and reason | Customer retries |
| Failed after entering a code | Authentication failed | Status N or R | Customer and bank |
| Paid, but no order | Webhook failed or not processed | Delivery log and order logs | Your developers |
| Money pending, order failed | Authorisation hold | Attempt status, any reversal | Issuer releases it |
| Charged twice | Duplicate attempts, or a hold plus a charge | All attempts and captures | Your payments team |
| Method pending for a long time | Delayed confirmation on a local method | Provider status | Wait, then escalate |
| Many customers, one bank | Issuer or network problem | Group by BIN, then escalate | Your provider |
| Many customers, one provider | Provider incident | Provider status page | Your payments team |
| Small repeated failures, odd names | Card testing | Attempts by BIN and IP | Your risk team |
When a symptom appears more than a few times a week, stop treating it as a ticket. Add it to your decline dashboard and fix the cause.
How does Omno help investigate a failed payment?
Omno keeps the whole story of a payment in one place. The transaction trail on the reporting and analytics page shows every attempt, cascade step, provider response and webhook, alongside a customer profile, so support can follow one payment across providers without opening each dashboard. Role-based access lets support see what it needs, and custom decline messages tell customers what to do next.
Key facts
- Stripe sorts failed payments into three types: issuer declines, blocked payments and invalid API calls (Stripe docs, 2026).
- Stripe says issuers categorise most declines as generic, and discuss the specifics only with their cardholders (Stripe docs, 2026).
- Stripe says that for some card types, a customer may see an authorisation for a blocked payment, which the issuer typically removes within a few days (Stripe docs, 2026).
- Stripe retries webhook delivery for up to three days in live mode, and doesn't guarantee events arrive in order (Stripe docs, 2026).
- Adyen retries failed webhooks on a schedule, then keeps retrying from a queue for up to 30 days (Adyen docs, 2026).
- Stripe's mobile SDKs require a 3-D Secure timeout of at least 5 minutes, covering network time and customer input (Stripe docs, 2026).
Changelog
- First published.
Sources
12 references
- Declines, Stripe
- Card declines, Stripe
- Payment status updates, Stripe
- Authenticate with 3D Secure, Stripe
- Charge.PaymentMethodDetails.Card.ThreeDSecure, Stripe
- Receive Stripe events in your webhook endpoint, Stripe
- Idempotent requests, Stripe
- 3D Secure API reference, Adyen
- Payment result codes, Adyen
- Refusal reasons, Adyen
- Troubleshoot webhooks, Adyen
- Visa Core Rules and Visa Product and Service Rules, 24 October 2026, Visa