MoniWave

Transaction lifecycle

Collections, payouts, and refunds are all transactions and share one lifecycle.

Statuses

accepted ──> submitted ──> completed
    │            │
    │            ├────────> failed
    │            │
    │            └────────> expired
    └─────────────────────> failed        (rejected at submission)
StatusMeaning
acceptedWe validated and recorded your request. Nothing has reached the mobile-money operator yet.
submittedHanded to the operator. For a collection, the customer is being prompted on their phone.
completedTerminal. The money moved.
failedTerminal. It did not, and will not. A reason code says why.
expiredTerminal. No outcome within the time limit — typically the customer never responded to the prompt.

Terminal means terminal

completed, failed, and expired are absorbing: once reached, a transaction's status never changes again. Not by a late operator callback, not by a retry, not by anything.

This matters for your integration: once you observe a terminal status you can act on it irreversibly — ship the goods, credit the account — without worrying that it will be reversed later. If an operator subsequently reports something contradictory, we record that contradiction for reconciliation and raise it internally; your transaction's status stays as it was.

Why you should poll

Operator callbacks are a hint, never proof. Mobile-money networks deliver them late, twice, or not at all, and a callback claiming success is not evidence that money moved.

So MoniWave does not treat a callback as authoritative either. A callback triggers a status check against the operator, and only that confirmed check drives a transaction to a terminal state.

For you, the rule is the same:

  • GET the transaction is authoritative. Whatever it returns is the truth.
  • Webhooks are a convenience so you don't have to poll in a tight loop. Every event obtainable by webhook is obtainable by polling.
  • Never build an integration that only works if webhooks arrive. A missed webhook must never mean a lost transaction — poll for anything still non-terminal after a reasonable interval.

Timing

PhaseTypicalNotes
acceptedsubmittedunder a secondWe hand off asynchronously.
submitted → terminalseconds to a few minutesDepends on how fast the customer responds.
expired~15 minutesNon-terminal transactions are expired by a sweeper.

Poll with backoff rather than tightly: a few seconds initially, widening to ~30–60 s. There is no benefit to polling faster than the customer can physically approve a prompt.

Every transaction keeps a full history

Internally, a transaction's status is not a stored field that gets overwritten — it is derived from an append-only sequence of recorded facts: requested, submitted, callback received, status checked, outcome confirmed, money posted, webhook dispatched.

Two consequences you can rely on:

  1. Nothing is silently overwritten. Every state change has a recorded cause, retained for disputes.
  2. We can always answer "what did you know and when?" — useful when reconciling with an operator or investigating a customer complaint.

The dashboard shows this timeline on each transaction's detail page, with the actor behind every step. While a transaction is still live the page follows it, so you can watch it settle rather than reloading.

Refunds do not change the original

A refund is its own transaction with its own lifecycle, linked to the original collection. Refunding a collection never changes that collection's status: a fully refunded collection stays completed, and the refund appears alongside it. See refunds.md.