Mobile money APIs are not payment rails. Build accordingly.
Timeouts, duplicate callbacks and pending states are normal operating conditions. Your integration should assume them.
2 min read
The first integration with a mobile money collection API usually takes an afternoon. You send a request, the customer approves a prompt on their phone, a callback arrives, the order is marked paid. Demo complete.
Then production begins, and the afternoon's assumptions start failing one by one. The request times out but the customer was charged. The callback arrives twice. The callback never arrives. The customer approves after your session has expired.
None of these are bugs in the provider. They are the normal behaviour of a system spanning a telecom network, a wallet platform and your servers. The integration has to be designed for them.
Give every attempt an identity
Before calling the provider, record the attempt in your own database with a reference you generate. Send that reference to the provider. Everything that follows — callbacks, status checks, reconciliation — is matched on it.
const attempt = await db.paymentAttempts.insert({
reference: crypto.randomUUID(),
orderId,
amountXaf,
status: "initiated",
});
await provider.requestToPay({
externalId: attempt.reference, // our key, not theirs
amount: amountXaf,
payer: msisdn,
});
If the request times out, you still know the attempt exists, and you can ask the provider about it by reference instead of guessing.
Make state transitions one-way
A payment attempt moves forward only: initiated, then pending, then succeeded or failed. A duplicate or out-of-order callback that tries to move it backwards is ignored. Crediting the customer happens exactly once, on the transition into succeeded, inside the same transaction.
Never trust a single signal
Callbacks are a convenience, not a guarantee. Alongside them, run a poller that checks every attempt stuck in pending for more than a few minutes, and a daily job that reconciles your records against the provider's statement. Most of the time, all three agree. When they do not, you want to find out from a report, not from a customer.
Design the pending state
Finally, give pending a real place in the product. Tell the customer the payment is being confirmed, let them leave the screen, and notify them when it resolves. A clear pending state turns a network hiccup into a short wait instead of a support ticket and a double payment.