> ## 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.

# Confirming Payment Results

> How to reliably determine whether a payment succeeded: the roles of redirects, webUrl, webhooks and Order Inquiry, what each orderStatus means, and what to do when no notification arrives.

The questions on this page come directly from real merchant conversations.
The single most important rule:

> **A redirect only means the user's browser came back to your site. It is
> never proof of payment.** The authoritative source of a payment result is
> the [Webhook notification](/docs/en/developer-docs/webhook/overview), with the
> Order Inquiry API as the reconciliation fallback.

## The user was redirected back to my site — is the payment successful?

Not necessarily. `successRedirectUrl` is UI navigation only. Confirm the
result through either of these, in order of preference:

1. **Webhook** — Waffo posts the payment result to your `notifyUrl` when the
   payment completes or fails.
2. **Order Inquiry** — call `/api/v1/order/inquiry` to actively query the
   order when you need to reconcile.

Mark an order as paid only when one of these reports a final success.

## The user closed the browser before being redirected — did the payment fail?

No. The payment may still complete on the payment method's side. The webhook
fires regardless of what the user's browser does. If in doubt, call Order
Inquiry.

## What is the difference between `webUrl` and `successRedirectUrl`?

They travel in opposite directions:

| Field | Direction | Meaning |
| - | - | - |
| `webUrl` (inside `orderAction`) | You send the user **to** the payment | When `orderStatus` is `AUTHORIZATION_REQUIRED` and `actionType` is `WEB`, redirect the user to `webUrl` to authorize / pay. For `DEEPLINK`, use `deeplinkUrl` instead. |
| `successRedirectUrl` (order creation parameter) | The user comes **back** after payment | Where the user lands after a successful payment. Optional — if omitted, the user stays on the Waffo cashier page. |

`failedRedirectUrl` and `cancelRedirectUrl` cover the failure and
user-cancellation returns in the same way.

## What does each `orderStatus` value mean?

| Status | Meaning |
| - | - |
| `PAY_IN_PROGRESS` | Order accepted; the user is in the process of paying. |
| `AUTHORIZATION_REQUIRED` | The payment method needs user authorization — bring the user to the page indicated by `orderAction`. |
| `AUTHED_WAITING_CAPTURE` | Authorized and waiting for merchant capture (card payments). |
| `PAY_SUCCESS` | Final success. |
| `ORDER_CLOSE` | Final close — the order was cancelled, failed, or timed out. |

Only `PAY_SUCCESS` and `ORDER_CLOSE` are final states.

## The order timed out — `TIMEOUT_CLOSE` vs `TIMEOUT_CHANNEL_CLOSE`?

Both are failure codes on orders closed by timeout. They tell you **where
the user dropped off**:

| Failure code | What happened |
| - | - |
| `TIMEOUT_CLOSE` | The user abandoned the payment on the Waffo cashier / payment page — no request was ever initiated towards the payment service. |
| `TIMEOUT_CHANNEL_CLOSE` ("Timeout waiting for channel response") | A payment or verification request (for example 3DS or a wallet redirect) was already initiated, but no result came back before the order timed out. |

Use the split to analyse your conversion funnel: `TIMEOUT_CLOSE` means
drop-off before authorization started, `TIMEOUT_CHANNEL_CLOSE` means the
flow stalled after it started. When Waffo cannot determine whether a request
was initiated, the order falls back to `TIMEOUT_CLOSE`. The distinction
applies to all payment methods; older orders keep their original code.

## Bank transfer / over-the-counter payments: why two notifications?

Payment methods where the user pays outside the checkout session — bank
transfer / virtual account (VA) and over-the-counter (OTC) — report in two
stages:

1. **Acceptance** — after order creation you receive an acceptance result.
   This is **not a final payment status**: the user has not paid yet.
2. **Payment result** — the final notification arrives only after the user
   completes the payment at their bank, ATM, or the counter.

The gap between the two can be long, and the automatic close time differs
per payment method. Treat only `PAY_SUCCESS` / `ORDER_CLOSE` as final,
exactly as for synchronous methods.

## I did not receive a webhook. What should I check?

1. `notifyUrl` must be **HTTPS (TLS 1.2+)** and reachable from the public
   internet.
2. Verify the `X-SIGNATURE` header with the **Waffo public key** — and make
   sure your endpoint isn't rejecting valid requests.
3. Respond with `{"message": "success"}` after processing. If Waffo receives
   `failed` or `unknown`, Waffo retries the notification on a back-off
   schedule spread over the following day — see
   [Webhook retry](/docs/en/developer-docs/webhook/retry) for the schedule.
4. While investigating, use Order Inquiry to fetch the order state so your
   business flow is not blocked.

## When should I call Order Inquiry instead of waiting?

* As a **reconciliation fallback** when no webhook has arrived within your
  expected window.
* When you receive an `Exxxxx` unknown error on a request — retry the inquiry
  until you get a final `PAY_SUCCESS` or `ORDER_CLOSE`.
* In batch reconciliation jobs.

Order Inquiry and webhooks return the same order state; they never disagree
on a final status.

## The order creation failed. Will I still get a notification?

Yes. `PAYMENT_NOTIFICATION` is sent when a payment **completes or fails** — a
failed order is notified exactly like a successful one, so do not build your
flow on the synchronous create response alone.

The notification is also richer than that response:

| Where you look | What you get |
| - | - |
| `/api/v1/order/create` response | `paymentRequestId`, `merchantOrderId`, `acquiringOrderId`, `orderStatus`, `orderAction` (plus `x402Info` for stablecoin orders) |
| `PAYMENT_NOTIFICATION` (and Order Inquiry) | The full order object, including **`orderFailedReason`** — a JSON value carrying `orderFailedCode` and `orderFailedDescription` |

So the failure reason for a rejected order is available in the notification
and through Order Inquiry, not in the create response. Match the notification
to your own record with `paymentRequestId` or `merchantOrderId`; both are
echoed back. Acknowledge it the same way as any other webhook — see
[event type list](/docs/en/developer-docs/webhook/event-types).

## How long can the customer still pay after I create the order?

`orderExpiredAt` is optional. If you omit it, the default expiry time from
your merchant contract applies. What it controls is narrower than it looks:

* It governs the window in which the customer can **submit the order on the
  checkout page** (must be UTC+0).
* Once the order has been submitted to the payment method, the expiry is
  governed by **that payment method**, not by your value. A small number of
  payment methods accept a channel-side expiry time from the merchant — the
  Payin page in the Merchant Portal shows the expiry behavior per payment
  method.
* Passing a time that has already elapsed is rejected at creation with
  `A0034` ("the order expiry time provided by the merchant has expired").

The practical consequence: your close time and the payment method's close
time are not the same clock, which is why a payment can still land after
Waffo has closed the order — see the last section on this page for how to
handle that case.

## An offline or bank-transfer order has been pending for hours. Should I cancel it?

No. For over-the-counter (OTC) and virtual-account / bank-transfer payments
the customer pays outside the checkout session, so a long pending period is
normal rather than a symptom of failure — and the automatic close time
differs per payment method.

Do not infer failure from elapsed time, and do not release or void the
purchase on your side while the order is still open: wait for the final
notification, or poll Order Inquiry. Only `PAY_SUCCESS` and `ORDER_CLOSE`
are final.

## I sent the same request twice at the same time and got an error. Did either go through?

A concurrency rejection is protection, not a business failure. Creating the
same order from two requests in parallel returns `A0040` ("the order cannot
be created concurrently") instead of creating two orders.

Handle it like this:

1. **Serialize calls per order** — one in-flight request per order at a time,
   rather than parallel retries.
2. **Resolve the state, do not guess it.** Call Order Inquiry to see what the
   order actually is before deciding anything.
3. When you retry the original request, reuse the same `paymentRequestId` —
   it is the idempotency key. See the
   [error code quick reference](/docs/en/developer-docs/tools-and-references/developer-tools/error-codes)
   for `A0011` and for `E`-class unknown-status handling.

## The user says they paid, but the order shows failed or timed out

Occasionally a payment completes on the payment method's side after the
Waffo order has already been closed as timed out ("failed first, succeeded
later"). How to handle it:

* **Never fulfil based on a user's payment screenshot.** Fulfil only when a
  webhook or Order Inquiry reports `PAY_SUCCESS`.
* Report the case to Waffo support with the order IDs. Waffo reconciles the
  order with the payment method side; the payment is then either confirmed
  to you or the debited amount is returned to the user.
* The user may be asked for additional details to locate the transaction,
  so collect what they can share — payment time, amount, and the payment
  method's transaction reference.


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