Skip to content

"Unsure" is not "failed": handling unknown payout responses

Remittance platform, client project, September 2026. Go, PostgreSQL.

Problem

A payout to a mobile-money wallet is sent to a vendor, and the vendor answers asynchronously. Sometimes the answer is a response the system does not recognise. The first version of the code treated anything unrecognised as a permanent decline.

Context

The platform moves money from senders in the US and UK to recipients in Africa and Asia, over bank and mobile-money rails. A payout is the last irreversible step: once the vendor has paid a wallet, the money is gone. I was the sole engineer on the backend.

Decision

Model an unrecognised response as its own state, unsure, instead of folding it into declined. An unsure payout is never resent until its real state is known.

Trade-offs

Mapping unknowns to "declined" is simple and keeps the state machine small, but it invites a retry or a reroute to another rail. If the first send actually succeeded, the recipient is paid twice. Mapping unknowns to "succeeded" is worse: the sender is charged and nobody is paid. A third state costs more code and an operations queue for payouts that stay unsure, but it is the only option that is wrong in neither direction.

Implementation

  • Unrecognised vendor responses become unsure, not a permanent decline.
  • Before any resend, the system polls the vendor for the payout's status. A payout that might still be live is never rerouted to another rail.
  • The payout provider interface gained a way to confirm state before a retry, so every rail has to answer "did this already happen?" before sending again.
  • Workers lease payout rows to claim them, and each payout settles in its own transaction, so one stuck payout cannot hold up the rest of the queue.

Result

Ambiguity now shows up as a visible state that someone can resolve, instead of a silent decline that could trigger a duplicate payment.

What I learned

When a side effect is irreversible, "I don't know" has to be a real state. Confirm before acting again, and never let an unknown resolve itself to whichever answer is easiest to code.