Skip to main content
Confirming Payment Results 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: 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 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. 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? 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.
  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. 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: 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: 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?

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?