# Transaction and settlement statuses

> Every payin, payout, refund and settlement status, which are final, and what to do about each.

Source: https://hub.ozow.com/integration-methods/statuses/

Every payment through Ozow moves through a series of statuses. This page lists all of them, tells
you which ones are final, and tells you what to do about each.

> 🚨 **Read this first.** `Pending` and `Complete` appear on a payin, on a settlement and on a
> refund. `Complete` on a payin is a customer's payment succeeding, on a settlement it is the
> payment out to you confirmed by Ozow's bank, and on a refund it is your customer having their
> money back. Refunds spell theirs as integers rather than strings. A status value on its own tells
> you nothing. Always check which object it belongs to before you act on it.

## How the lifecycles relate

A payment has more than one lifecycle. The transaction completing and the money reaching your bank
account are two separate events with two separate status vocabularies.

```mermaid
flowchart TB
    subgraph in["Money in"]
        direction LR
        A["Customer<br/>pays"] --> B["Payin<br/>Complete"]
        B --> C["Settlement<br/>Complete"]
        C --> D["Your bank<br/>account"]
    end
    subgraph out["Money out"]
        direction LR
        T["Your bank<br/>account"] -. "top up" .-> E["Your<br/>float"]
        E --> F["PayoutComplete"]
        E --> H["Refund"]
        F --> G["Recipient's<br/>bank account"]
        H --> G
    end
    in ~~~ out
```

**A completed transaction is not settled money.** A payin status of `Complete` means the customer's
payment succeeded and the funds *will* be settled to you. The settlement status tells you whether
that has actually happened. Don't use payin status to reconcile your bank account. Every other final
payin status settles nothing.

**Refunds and payouts draw on your float, not on the original transaction.** A refund needs a funded
float balance even though it's returning money the customer already paid, because that money has
already been settled to you. See [Float
top-up](https://hub.ozow.com/payment-products/settlements-and-float/float-top-up.md).

## Look up a status

[Status decoder](https://hub.ozow.com/integration-methods/statuses/), a tool on this page.

The tables below list every status in full.

## How to read a status

### Final vs non-final

A **final** status will not change on its own. A **non-final** status will be followed by another
update, and you must wait for it rather than acting.

Getting this wrong is the most common integration bug in payments. Never release goods, mark an
order paid, or notify a customer on a non-final status.

> ⚠️ One exception: payin `PendingInvestigation` is final in the sense that no automatic update is
> coming, but Ozow Support can change it to `Complete` or `Error` after a manual check. Treat it as
> needing human action, not as a settled outcome.

### Status and sub-status

Payouts return a status and, in most cases, a sub-status. **Branch on the sub-status whenever one is
present.**

**The API sends the code, not the name.** `subStatus` on a payout status response is a number, so
`405` is what arrives where this page says `PayoutProcessingError_InvalidAccountNumber`. Both are
listed. The name is for reading; the code is what your `switch` matches.

This matters because a parent status can be non-final while the sub-status under it is final.
`PayoutReceived` is non-final, but `Payout_ValidationFailed` beneath it is a final failure. If you
only read the parent status, you will wait forever for a payout that has already failed.

### Never infer status from the browser redirect

The customer's browser returning to your success page is not proof of payment. Statuses arrive on
your notification or webhook URL, and you can query them with the status check API. See [Building a
secure integration](https://hub.ozow.com/getting-started/building-a-secure-integration.md).

---

## Payin statuses

```mermaid
stateDiagram-v2
    direction LR

    state "Not paid" as NotPaid {
        Cancelled
        Abandoned
        Voided
        Error
    }

    [*] --> Created
    Created --> PendingInvestigation
    Created --> Pending
    Created --> Complete
    Created --> NotPaid

    PendingInvestigation --> Complete
    PendingInvestigation --> NotPaid

    Pending --> Complete
    Pending --> NotPaid
```

Which of the four unpaid outcomes you can get depends on where the payment was when it stopped.
`Voided` follows only `Created`. From `Pending` the only unpaid outcome is `Error`, and from
`PendingInvestigation` it is `Cancelled`, `Abandoned` or `Error`.

**Non-final:** `Created`, `Pending`
**Final:** `Complete`, `PendingInvestigation`, `Cancelled`, `Abandoned`, `Voided`, `Error`

**The `One API` column is the value `GET /payments/{id}/transactions` returns for that status.**

| Status | One API | Final? | Settles? | What it means | What to do |
|---|---|---|---|---|---|
| `Created` | `Incomplete` | No | No | The transaction has been created and the customer has opened the payment page, but hasn't completed it. | Wait for a final status. |
| `Complete` | `Successful` | Yes | **Yes** | The payment succeeded and the funds will be settled to you. | Nothing. Fulfil the order. |
| `Cancelled` | `Error` | Yes | No | The transaction was cancelled: either the customer pressed cancel, or the payment failed Ozow's verification with the bank. `StatusMessage` says which. | Nothing. If they still want to pay, ask them to start a new transaction. |
| `Abandoned` | `Error` | Yes | No | The customer started the transaction but didn't finish it, typically they closed the Ozow payment page. | Nothing. If they still want to pay, ask them to start a new transaction. |
| `Voided` | `Error` | Yes | No | The transaction was invalidated and won't be processed. Happens when the customer changes bank partway through the Ozow flow, or when the saved profile they chose is deleted, deactivated or fails to load. | Nothing. A new transaction will have been created for the method they switched to. **No notification is sent for a voided transaction**, so find these by polling rather than by waiting. |
| `Error` | `Error` | Yes | No | An error occurred while the transaction was being processed. Not caused by anything the customer did. | Ask the customer to retry the transaction. |
| `Pending` | `Pending` | No | No | The payment still has to be verified. The outcome follows once it has been. | Wait for the update on your notification URL. Do not release anything on it. |
| `PendingInvestigation` | `Pending` | Yes | No | Ozow could not complete its verification with the bank, so the payment has to be checked manually against your bank statement. | Check your bank statement. If the funds arrived, contact Ozow Support with proof of payment to have the status updated to `Complete`. If they didn't, contact Support to have it set to `Error`. |

> ℹ️ `Pending` is non-final: an update follows. Handle it as "wait", never as an outcome. It means
> neither that the payment failed nor that it succeeded.

> ⚠️ Refunding a transaction does not change its status. If you need to know whether a transaction
> has been refunded, check the refund records rather than relying on the transaction status.

> ℹ️ Settlement timing depends on the payment method.

---

## Payout statuses

```mermaid
stateDiagram-v2
    direction LR

    state "Ended without paying" as Ended {
        PayoutProcessingError
        PayoutReturned
    }

    [*] --> PayoutReceived
    PayoutReceived --> Verification
    Verification --> SubmittedForProcessing
    SubmittedForProcessing --> PayoutComplete
    PayoutComplete --> PayoutReturned

    Verification --> Ended
    SubmittedForProcessing --> Ended

    Verification --> PayoutPendingInvestigation
    SubmittedForProcessing --> PayoutPendingInvestigation
    PayoutPendingInvestigation --> PayoutComplete
    PayoutPendingInvestigation --> Ended
```

The happy path is `PayoutReceived` → `Verification` → `SubmittedForProcessing` → `PayoutComplete`.
`Verification` can also reach `PayoutComplete` directly, so a payout that never appears in
`SubmittedForProcessing` has not skipped a step. `PayoutProcessingError` and `PayoutReturned` are
grouped because nothing follows either of them.

> ⚠️ **`PayoutComplete` is not final, though it almost never changes.** A completed payout moves
> to `PayoutReturned` if the destination bank sends the money back, which is a rare event rather
> than one to plan a flow around. Keep handling status updates for a payout you have already marked
> paid, and you will hear about it on the day it happens. The only two statuses nothing follows are
> `PayoutProcessingError` and `PayoutReturned`.

**Payout timing:** with `isRtc` set to `true` the payout is instant. With `isRtc` set to `false` it
takes 1-2 business days.

> ⚠️ Branch on the **sub-status**, not the parent status. `PayoutReceived`, `Verification` and
> `PayoutPendingInvestigation` are all non-final while carrying sub-statuses that are final
> failures.

### PayoutReceived

| Sub-status | Code | Final? | What it means | What to do |
|---|---|---|---|---|
| None | None | No | The payout request has been received. | Wait for a final status. |
| `Payout_Unclassified` | 100 | Yes | No sub-status could be determined. The payout has failed. | Rare edge case. Treat as a failure and investigate. |
| `Payout_ValidationFailed` | 101 | Yes | Request validation failed. | Check the `ErrorMessage` field for the reason, correct the request, then resubmit. |

### Verification

| Sub-status | Code | Final? | What it means | What to do |
|---|---|---|---|---|
| None | None | No | The payout is being verified. | Wait for a final status. |
| `Verification_Pending` | 201 | No | Ozow is waiting for a response from your verification webhook. | Wait for a final status. |
| `Verification_Success` | 203 | No | Your webhook verified the payout successfully. Processing continues. | Wait for a final status. |
| `Verification_Failed` | 202 | Yes | Your verification webhook returned a response that failed verification. | Check why your webhook rejected it, then resubmit the payout. |
| `Verification_Error` | 204 | Yes | Ozow couldn't reach your verification webhook. | Check that your webhook is reachable. If you've changed its URL, confirm Ozow has the new one. Then resubmit. |
| `Verification_AccountNumberDecryptionFailed` | 205 | Yes | The key returned by your webhook failed to decrypt the account number. | Check your webhook's key handling, then resubmit the payout. |
| `Verification_Success_Awaiting_Funds` | 206 | No | The payout verified, but your float balance is too low to cover it. | Top up your float. The payout continues on its own once the funds are there. Do not resubmit. |
| `Verification_Success_Awaiting_Submission` | 207 | No | The payout verified and is queued for submission. | Wait for a final status. |

### SubmittedForProcessing

| Sub-status | Code | Final? | What it means | What to do |
|---|---|---|---|---|
| None | None | No | The payout is being processed. | Wait for a final status. |
| `SubmittedForProcessing_PayoutAddedToBatch` | 301 | No | The payout has been added to a batch. | Wait for a final status. |
| `SubmittedForProcessing_PayoutSubmittedToBank` | 302 | No | The batch has been processed and submitted to the bank. | Wait for a final status. |
| `SubmittedForProcessing_PayoutSubmittedToPpi` | 303 | No | The payout has been submitted for processing. | Wait for a final status. |

### PayoutComplete

| Sub-status | Code | Final? | What it means | What to do |
|---|---|---|---|---|
| None | None | No | The payout completed successfully. | Release whatever the payout was for, and keep handling updates: this can still become `PayoutReturned`. |

### PayoutProcessingError

| Sub-status | Code | Final? | What it means | What to do |
|---|---|---|---|---|
| None | None | Yes | An error occurred while processing the payout. | Resubmit the payout. |
| `PayoutProcessingError_PayoutRejected` | 401 | Yes | The bank rejected the payout. | Resubmit the payout. |
| `PayoutProcessingError_PayoutCancelled` | 402 | Yes | Ozow stopped the payout before it was paid. | Check the `ErrorMessage` field, then resubmit. |
| `PayoutProcessingError_Insufficient_Balance` | 403 | Yes | Your float balance was too low to cover the payout. | **Top up your float. Do not resubmit**: see the warning below. |
| `PayoutProcessingError_PayoutInternalError` | 404 | Yes | An internal error occurred during processing. | Resubmit the payout. |
| `PayoutProcessingError_InvalidAccountNumber` | 405 | Yes | The account number is invalid. | Correct the account number, then resubmit. |

> 🚨 **On `PayoutProcessingError_Insufficient_Balance` you must not resubmit.** Top up your float
> instead. Once the float is allocated, the payout processes automatically. Resubmitting risks
> paying the recipient twice.

### PayoutReturned

| Sub-status | Code | Final? | What it means | What to do |
|---|---|---|---|---|
| None | None | Yes | The payout couldn't be paid into the recipient's account. | Resubmit the payout. |
| `PayoutReturned_Unpaid` | 9001 | Yes | The destination bank rejected the payment. | Check that the destination account is still active, then resubmit. |

### PayoutPendingInvestigation

| Sub-status | Code | Final? | What it means | What to do |
|---|---|---|---|---|
| None | None | No | The payout is under investigation. | Wait for a final status. |
| `PayoutPendingInvestigation_AmountMismatch` | 601 | Yes | The payout failed because the amounts didn't match. | Check the payout details, then resubmit. |

> ⚠️ **A payout cannot be cancelled once it is submitted.** Check the request before you send it:
> the account number, the amount and the reference are all final from that point.

---

## Refund statuses

A refund returns money to a customer for a payin you've already received. Refunds draw on your float
balance, not on the original transaction; the funds from that transaction have already been settled
to you.

> ⚠️ Refund statuses are returned as **integers**, not strings. This is different from payin, payout
> and settlement statuses, which are returned as text.

> ⚠️ **The numeric values are not in lifecycle order.** A refund progresses `0` → `2` → `1`. Don't
> treat a higher value as further along, and don't use greater-than comparisons to test progress.

```mermaid
stateDiagram-v2
    [*] --> Pending: 0
    Pending --> Submitted: 2
    Pending --> Cancelled: 4
    Submitted --> Complete: 1
    Submitted --> Failed: 3
    Submitted --> Returned: 5
```

This is the path a refund takes. Three further statuses sit outside it, `Invalid` (-1),
`PendingInvestigation` (-2) and `Error` (-3). Each one has a row below. Handle them wherever they
turn up rather than by position.

**Non-final:** `Pending` (0), `Submitted` (2), `PendingInvestigation` (-2)
**Final:** `Complete` (1), `Failed` (3), `Cancelled` (4), `Returned` (5), `Invalid` (-1), `Error` (-3)

**The negative values are real.** `Invalid`, `PendingInvestigation` and `Error` are returned by the
API alongside the six above. Switch on all nine, or give your default branch something safe to do.

| Value | Status | Final? | What it means | What to do |
|---|---|---|---|---|
| `0` | `Pending` | No | The refund request has been submitted and accepted, but not yet processed. | Wait for a final status. |
| `2` | `Submitted` | No | The refund has been assigned to a batch and is being processed. | Wait for a final status. |
| `1` | `Complete` | Yes | The refund was paid successfully. The customer has their money. | Nothing. |
| `3` | `Failed` | Yes | The refund payment failed. | Check that your float is funded, then submit a new refund. |
| `4` | `Cancelled` | Yes | The refund was cancelled before it was submitted for processing. | Nothing. If the customer is still owed a refund, submit a new one. |
| `5` | `Returned` | Yes | The refund was paid and came back. The destination account did not accept the credit, or the destination bank could not apply it. | Confirm the account details with the customer before you try again. Resubmitting the same details without checking returns the money a second time. |
| `-1` | `Invalid` | Yes | The refund could not be accepted as submitted. | Check the refund details against the original transaction, then submit a corrected refund. |
| `-2` | `PendingInvestigation` | No | The refund is being checked manually. | Wait. Ozow reports this as `Pending` on a `thin` webhook, so a handler reading the webhook rather than the API will not see this value. |
| `-3` | `Error` | Yes | The refund failed because of an error rather than a rejection. | Ozow reports this as `Failed` on a `thin` webhook. Treat it as a failure and submit a new refund. |

> ⚠️ **On `Returned`, do not resubmit the same details without checking them.** The money came back
> from the destination, so sending it again the same way returns it again. Confirm the account with
> the customer first.

> ℹ️ Refunds require a funded float. If your float is empty, refunds will not process. See [Float top-up](https://hub.ozow.com/payment-products/settlements-and-float/float-top-up.md).

> ℹ️ **Refund status is the source of truth for whether a customer has been refunded.** The original
> payin transaction keeps the status it had, so don't use transaction status to check refund state.

---

## Settlement statuses

A settlement is the transfer of your collected funds into your bank account. Settlement statuses
tell you where that transfer is, and they are **not** the same thing as the status of the
transactions inside it.

```mermaid
stateDiagram-v2
    direction LR
    [*] --> Pending
    Pending --> Submitted
    Submitted --> Complete
    Submitted --> PendingInvestigation
    PendingInvestigation --> Complete
```

**Non-final:** `Pending`, `Submitted`, `PendingInvestigation`
**Final:** `Complete`

**A settlement has four statuses, and none of them is a payin status.**

| Status | Final? | What it means | What to do |
|---|---|---|---|
| `Pending` | No | The settlement has been created. The payment out to you has not started. | Wait. If it stays here for an extended period, email [support@ozow.com](mailto:support@ozow.com). |
| `Submitted` | No | The settlement has been submitted to Ozow's bank. It might not have reached yours yet. | Wait. If it stays here for an extended period, email [support@ozow.com](mailto:support@ozow.com). |
| `Complete` | Yes | Ozow has submitted the payment and confirmed with Ozow's bank that it went out. Your own bank still has to clear it. | Reconcile against your bank statement rather than against this status. `Complete` is Ozow's leg finished; the clearing leg is your bank's and can lag it. |
| `PendingInvestigation` | No | The settlement is being checked manually. | Wait. If it stays here for an extended period, email [support@ozow.com](mailto:support@ozow.com) with your settlement reference. |

Settlement timing depends on the payment method the funds came in through.

---

## Which statuses mean money moved

Only these mean money moved:

- **Payin:** `Complete`, and only `Complete` settles
- **Payout:** `PayoutComplete`, which can still become `PayoutReturned` if the bank sends it back
- **Refund:** `Complete` (`1`), the integer and not the string
- **Settlement:** `Complete`, which is the payment out confirmed by Ozow's bank, with your own bank
  still to clear it

Everything else is either in progress or a failure. In particular:

- `Pending`, `Created`, `Verification`, `SubmittedForProcessing` and `PayoutReceived` are **in
  progress**. Wait.
- A payin `PendingInvestigation` needs a human. It is not a success.
- A payin `Complete` is not settled money. Check the settlement status for that.