> ## Documentation Index
> Fetch the complete documentation index at: https://waffo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Timeouts & When a Result Is Final

> Why an order that timed out is not proof the payment failed: what Waffo does at close time, whose clock decides the timeout, which failures reach you as a notification and which only surface on inquiry, late notifications and retries, automatic returns after a late success, the separate 3DS authentication timeout, and how long your front end should wait.

[Confirming Payment Results](/docs/en/support/payment-results-faq) establishes the
rule this page depends on: **a redirect is never proof of payment, and the
authoritative result is the webhook with Order Inquiry as the fallback.** This
page is about the second half of that rule — *when* a result is allowed to be
treated as final, and why a timeout is not one of those moments.

> **The one sentence to take away:** a timed-out order means Waffo stopped
> waiting. It does not mean the payment method stopped, and it does not mean the
> user cannot still pay.

## My order timed out. Is the payment definitely not going to happen?

No. Timeout is Waffo's decision about your order, not the payment method's
decision about the payment.

For a number of payment methods the payment stays in a non-final state on the
method's side after your order has been closed — there is no automatic close on
their side at all. In that situation:

* The user can still open the payment page or app and complete the payment.
* The payment can succeed **after** the order shows as timed out.
* A result notification can arrive after the close.

So a timed-out order is a *provisional* failure. Do not destroy the purchase
record, do not release a reserved seat or licence irreversibly, and do not tell
the user "the payment failed and no money will be taken" — tell them the attempt
did not complete and that they should check before paying again.

## What does Waffo actually do when my order reaches its close time?

Two different things, depending on how far the payment got:

| How far the order got | What happens at close time |
| - | - |
| The user never submitted a payment — the order never reached a payment attempt | Waffo closes the order and reports the timeout to you |
| A payment attempt already exists for the order | The scheduled close **skips** it — the order is left to resolve on its real result rather than being forced to failed |

Where the payment method supports it, Waffo also asks the method to close the
payment at that point. Where it does not, the payment simply stays open on their
side until it expires by their own rules.

This is why `TIMEOUT_CLOSE` and `TIMEOUT_CHANNEL_CLOSE` mean different things —
see
[Confirming Payment Results](/docs/en/support/payment-results-faq) for the split. It
is also why the second row above matters more than merchants expect: an order
that stalled *after* authorization started is not going to be force-failed on a
timer.

## Whose clock decides whether a payment is late — mine or the payment method's?

The payment method's. The expiry you set with `orderExpiredAt` governs the
window in which the user can submit the order on the checkout page. Once the
payment has been handed to the payment method, **that method's own server time
decides** whether the payment arrived in time.

The practical effects:

* Network latency and retries on the method's side can push a payment past your
  window while still being on time by theirs.
* A payment that reaches the method just before or just after your cut-off can
  still be processed normally.
* Your close time and the payment method's close time are two different clocks,
  and they are not synchronised.

Treat your own expiry as a UI and housekeeping deadline, not as a financial
cut-off.

## Will I be notified when a payment fails or when the user never pays?

Yes when the **order** closes — but not necessarily when an individual **attempt**
fails, and the gap between those two is the single most common cause of "we never
heard anything" tickets.

| Outcome | Notification |
| - | - |
| Payment succeeds | `PAYMENT_NOTIFICATION` with `orderStatus` = `PAY_SUCCESS` |
| The order closes — on timeout, on cancel, or because the payment method reported a final failure | `PAYMENT_NOTIFICATION` with `orderStatus` = `ORDER_CLOSE` and the reason in `orderFailedReason`. Waffo sends this itself; it does not depend on the payment method |
| An attempt fails but the order stays open | **Nothing yet.** On several payment methods a failed attempt does not close the order — the user may retry inside it — so no notification is produced until the order itself reaches a final state |
| The user simply never pays | Nothing until the order's expiry closes it; then the `ORDER_CLOSE` notification above |

So the notification you may be waiting for is not missing — it is waiting for the
order to end. Until then, the only way to establish the state is to ask:
`/api/v1/order/inquiry`. Build the polling before you go live rather than after
the first reconciliation gap, and see
[What Can I Do to an Order in Each Status?](/docs/en/support/order-status-actions-and-capture-faq)
for what the close notification carries.

A workable pattern: poll every 30 seconds while the order is open, up to a
bounded number of attempts, then poll again once after your own close time to
catch a late success. Stop polling as soon as you have a final state.

## A notification arrived after I had already closed the order. Is that a bug?

No, it is expected behaviour on several payment methods. A method's
notification mechanism does not stop because your order expired. If the payment
succeeded, the notification is sent — and if it is not acknowledged, it is
**retried**, sometimes several times.

Your handler therefore has to accept a success notification for an order you
consider dead. Concretely:

1. Never reject a notification because the order is closed on your side. Accept
   it, acknowledge it, and reconcile afterwards.
2. Make the handler idempotent — retries mean the same event arrives more than
   once. See [webhook retries](/docs/en/developer-docs/webhook/retry).
3. Acknowledge with `Content-Type: application/json` and body
   `{"message":"success"}`, otherwise the retry schedule keeps firing.
4. Decide, once, what your business does with a late success: fulfil, or refund.
   Both are legitimate; having no rule is not.

## The user paid after the timeout. Do they get their money back automatically?

Sometimes. On some card-scheme wallet payment methods, a payment that lands
after the method's own payment timeout is **returned automatically** — the
method initiates the return itself and the amount goes back to the cardholder
without either you or Waffo doing anything. The return is not instant — it lands
on the cardholder's account on the method's own timetable. This is the payment
method's own behaviour, not a Waffo service level.

On most other methods there is **no** automatic return. A late success is a real
success: the money has moved, the order is payable, and if you do not want to
fulfil it you have to refund it — see
[Refund Limits by Payment Method](/docs/en/support/payment-method-refund-limits-faq).

Because both outcomes exist, do not tell a user "you will be refunded
automatically". Check the order first.

## How long should my front end wait before showing an error?

Set an explicit ceiling. A reasonable default for redirect-and-return flows is
**up to 5 minutes**, polling order status every few seconds while you wait.
After that:

* Stop the wait and show a neutral message: the payment could not be confirmed,
  and the user should retry.
* **Create a new order for the retry.** Do not reuse the original order.
* Keep your server-side polling running on the old order — the user's browser
  giving up says nothing about the payment.

What not to do: spin indefinitely on a waiting page, or show "payment failed"
the moment the wait expires. The former loses the user, the latter creates
duplicate payments.

## 3DS authentication timed out. Can I retry on the same order?

No. 3DS authentication carries **its own expiry, independent of your order
expiry** — the authentication link is valid only for a short, fixed period from
the moment it is generated, considerably shorter than a typical order expiry. If
the user does not finish authenticating in that window, the order lands in a
failed state for that reason and **cannot be reused**.

The correct recovery is to **create a new order**. Sending the user back to the
old authentication link, or re-submitting the original order, will not work:

| Situation | Action |
| - | - |
| User abandoned the 3DS page and comes back later | Create a new order |
| Authentication link expired | Create a new order |
| Authentication completed but no result yet | Wait / poll — this is not a timeout |

Note that a frictionless 3DS authentication also produces a redirect the user
passes through very quickly. Seeing that redirect is not, on its own, a result —
the same rule as everywhere else applies.

## A query said the payment failed, and a later query said it succeeded. Which one is right?

The later one. On several payment methods a failed attempt does **not** close the
order — the user is free to try again inside the same order, and a subsequent
attempt can succeed. A query run between the two attempts truthfully reports the
failed one.

An example of the sequence, with times:

| Time | What happened | What a query returns |
| - | - | - |
| 1:00 | The user's first attempt fails | — |
| 1:03 | You query | Failed |
| 1:05 | The user tries again and succeeds | — |
| 1:06 | You query | Succeeded |

The rule that follows: **a failure is only final once the order itself is final.**
While the order is still open, treat a failure result as "this attempt did not
work", not as "this order will never be paid". Concretely:

* Do not release inventory or cancel the purchase on the first failure.
* Do not stop polling on a failure while the order is open.
* Do show the user a retry path — on these methods retrying inside the existing
  order is exactly what is supposed to happen.

The failures that *are* final are the ones that come with a closed order
status, or with a rejection at order creation.

## What actually counts as final?

| Signal | Final? |
| - | - |
| Webhook payment result reporting a final order status | **Yes** |
| `/api/v1/order/inquiry` returning a final order status | **Yes** |
| Redirect back to your site | No |
| Your own timeout elapsing | No |
| An `E`xxxxx "unknown status" response | No — keep querying |
| The user's screenshot or word | No |

Only the first two rows may drive fulfilment, accounting, or a refund decision.
Everything else is a hint.

## What does a timeout-safe integration look like?

A checklist you can hold your implementation against:

1. `orderExpiredAt` is set deliberately, in UTC+0, and you know it only governs
   the checkout-page window.
2. Every order has server-side polling that survives the user closing the
   browser.
3. Your webhook handler accepts, acknowledges and reconciles notifications for
   orders you already closed.
4. The handler is idempotent on repeated delivery of the same event.
5. Retries create a **new** order; nothing reuses an expired one.
6. You have a written rule for a late success — fulfil or refund.
7. Nothing in your code treats elapsed time, a redirect, or an `E` code as a
   final result.

## Where do I look next?

| Question | Page |
| - | - |
| What each `orderStatus` means, `webUrl` vs `successRedirectUrl`, missing webhooks | [Confirming Payment Results](/docs/en/support/payment-results-faq) |
| Bank transfer and virtual-account orders that stay open for days | [Bank Transfers & Virtual Accounts](/docs/en/support/bank-transfer-and-virtual-account-faq) |
| Refunding a late success | [Refund Limits by Payment Method](/docs/en/support/payment-method-refund-limits-faq) |
| Error codes seen at order creation | [Error Codes & Limits Supplement](/docs/en/support/error-codes-and-limits-supplement) |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.