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

# Payment status

> How to find out whether a payment actually happened — webhooks, your transaction record, and asking the operator directly.

A collection does not complete when you call `/pay`. That call only pushes a prompt to
the customer's phone (or creates a Wave checkout). The customer then approves it, the
operator confirms to Kori, and only then is your balance credited.

So every payment has three possible states:

| State     | Means                                                 | What to do                               |
| --------- | ----------------------------------------------------- | ---------------------------------------- |
| `pending` | The prompt is out. The customer has not answered yet. | Wait. **Do not fulfill the order.**      |
| `success` | The operator confirmed. Your balance is credited.     | Fulfill the order.                       |
| `failed`  | The operator refused it, or the customer declined.    | Nothing was debited. Let them try again. |

<Warning>
  `pending` is **not** a failure. A payment can sit pending for minutes while the
  customer finds their phone. Treat it as unresolved, never as declined.
</Warning>

## 1. Webhooks — the way you should build

Subscribe to `payment_received` and let Kori tell you. No polling, no timers, and it
works when nobody has a browser open.

```json Webhook payload theme={null}
{
  "event": "payment_received",
  "data": { "transaction_id": 88213, "reference": "order_1024", "amount": 5000 }
}
```

See [Webhooks](/en/webhooks) for signature verification and retries.

## 2. Read your transaction record

```bash theme={null}
curl https://api.kori.ml/merchant/api/transactions/88213 \
  -H "Authorization: Bearer <token>"
```

Returns Kori's stored record, including `status`. This is what you use for a dashboard,
a reconciliation job, or any time you want the answer as Kori currently knows it.
Requires the `balance` scope.

## 3. Ask the operator now

```bash theme={null}
curl https://api.kori.ml/merchant/api/payment/status/order_1024
```

```json theme={null}
{ "message": "SUCCESS", "state": "success", "status": true }
```

This one queries the mobile-money operator directly and settles the transaction if it
has completed, so it is the authoritative answer while a payment is still open. Keyed by
`reference`, not by `transaction_id`.

It needs no authentication — the reference is the credential — which is what lets a
hosted checkout page poll it straight from the customer's browser.

<Note>
  Use this while a customer is waiting. For everything else, prefer the webhook: it
  costs nothing and arrives even if the customer closes the tab.
</Note>

## Polling, if you must

* Poll every **3 to 5 seconds**, no faster.
* Stop as soon as `state` is `success` or `failed`.
* Give up after about **two minutes** of waiting on a customer — but treat that as
  "still unknown", not as a failure.
* A `500` means the operator could not be reached. Retry; it says nothing about the
  payment.

Settlement is idempotent: the webhook, this endpoint, and Kori's own reconciler can all
resolve the same payment, and your balance is credited exactly once whichever gets there
first.

## Payments that are never confirmed

If a customer never answers the prompt, the payment stays `pending` and Kori marks it
`expired` after 24 hours. A late confirmation still credits you — `expired` means "we
stopped waiting", not "the money is gone".

<Warning>
  Never decide a payment failed because you stopped hearing about it. The only failures
  are `state: "failed"` and `status: "failed"`.
</Warning>
