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.
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 withorderExpiredAt 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.
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:- Never reject a notification because the order is closed on your side. Accept it, acknowledge it, and reconcile afterwards.
- Make the handler idempotent — retries mean the same event arrives more than once. See webhook retries.
- Acknowledge with
Content-Type: application/jsonand body{"message":"success"}, otherwise the retry schedule keeps firing. - 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.
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.
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:orderExpiredAtis set deliberately, in UTC+0, and you know it only governs the checkout-page window.- Every order has server-side polling that survives the user closing the browser.
- Your webhook handler accepts, acknowledges and reconciles notifications for orders you already closed.
- The handler is idempotent on repeated delivery of the same event.
- Retries create a new order; nothing reuses an expired one.
- You have a written rule for a late success — fulfil or refund.
- Nothing in your code treats elapsed time, a redirect, or an
Ecode as a final result.