When we build an online store for a Ugandan client, the checkout flow is never just "add a cart and plug in Stripe." The majority of buyers pay with mobile money — MTN MoMo or Airtel Money — and any integration that treats those as secondary will fail at the point of conversion. We design the payment layer around that reality, not around a global default.
Why mobile money is the default checkout
In our builds, MTN MoMo and Airtel Money cover the bulk of transactions, especially for the small-business and craft-store clients we work with in Kampala and the broader Central region. A checkout that assumes a credit card is the primary option is a checkout built for the wrong market. We typically wire mobile money first, cards second, and we keep the card option visible because some customers still prefer it — but we do not build the flow around it.
For a practical example, look at how we approached the Sweet Cakes & Confectioners store. Rather than forcing a full cart-and-account flow, the simplest effective checkout was a product page that handed off directly to WhatsApp with the order already filled in, then phone confirmation with a mobile-money prompt. That is not a fallback; it is the norm for many sellers here.
MTN MoMo and Airtel Money APIs
We have integrated both MTN MoMo and Airtel Money through their direct provider APIs — not only through aggregators — because direct access gives us clearer error codes and more control over retry logic. For MTN MoMo, the collection flow hits endpoints in the https://momoservices.mtn.com/collection/ family; the exact versioned path depends on your environment (sandbox versus production) and your provider agreement. We always test in sandbox first, and we never treat the production endpoint as interchangeable with the test URL.
A practical snippet for initiating a collection looks roughly like this, with the exact endpoint, authentication header, and payload fields varying by contract and SDK version:
curl -X POST https://momoservices.mtn.com/collection/v1_0/transaction \
-H "Authorization: Bearer $MTN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"amount": "150000",
"currency": "UGX",
"externalId": "order-8421",
"payer": {"partyIdType": "MSISDN", "partyId": "0771234567"},
"payee": {"partyIdType": "MSISDN", "partyId": "0777654321"},
"reason": "Sweet Cakes order 8421"
}'
We use externalId tied to our internal order number so we can reconcile without relying solely on the mobile-money receipt, which customers sometimes screenshot or miss. Airtel Money uses a different endpoint family (https://payments.airtel.co.ug/ or the aggregator’s mapped endpoint depending on your agreement), but the pattern is the same: generate a transaction reference, push to the customer’s phone, confirm via callback, and reconcile against our order record.
We run both integrations behind a unified wrapper in our backend so the storefront does not care which provider succeeds. The wrapper maps the provider-specific error (timeout, insufficient balance, wrong PIN) into a single message the customer sees: "Payment not completed — please try again or confirm with your PIN." That abstraction saves us from rewriting checkout logic per provider.
Card gateways: Stripe versus Flutterwave
When a client also needs card payments — usually for diaspora or corporate buyers — we compare Stripe and Flutterwave directly rather than assuming the global default is best.
Stripe’s Nigerian and Uganda coverage has improved, but for pure Ugandan shilling transactions, Flutterwave often has lower friction in local KYC and settlement to a Ugandan bank account. We have run both for clients. Stripe’s fee for African cards is typically around 3.9% + $0.30 per transaction; Flutterwave’s local card fee is often closer to 2.9% + a flat fee for UGX-denominated transactions, though exact rates depend on your merchant agreement and whether the card is local or international. We quote both numbers at the proposal stage, and we ask the client which settlement currency they prefer — UGX settled to a local bank, or USD settled to an offshore account — because the fee structure shifts with that choice.
A practical difference: Flutterwave’s Uganda integration supports direct Airtel and MTN Money rails through a single API, which can reduce provider count. Stripe requires separate mobile-money plumbing or reliance on a third-party processor to reach MoMo. For a store that needs both cards and mobile money, that sometimes makes Flutterwave the simpler operational choice, even if Stripe has a stronger card-only feature set.
We also test settlement timing in real conditions, not just documentation. A gateway that promises "instant settlement" can mean "same-day batch" or "T+1" depending on your bank partner. We confirm that with a test transaction before the store goes live.
Our integration workflow
We follow a consistent sequence for every new store:
- Confirm the client’s MTN and Airtel merchant accounts — without them, direct API integration is impossible.
- Set up sandbox keys for both mobile-money providers and for the card gateway (Stripe or Flutterwave).
- Build the unified wrapper with provider-specific retries: mobile-money timeouts are common, so we retry once with a different reference rather than showing a failure immediately.
- Test the callback path on our server using
ngrokor an exposed endpoint, verifying that the provider hits our URL correctly — this is where most integrations break in production, because the webhook URL changes or the firewall blocks it. If you need reliable HTTPS callbacks, see our wildcard SSL setup with Certbot and Cloudflare for infrastructure reference. - Run a live test with real mobile-money accounts and a real card, using small amounts (UGX 1,000 or less), and confirm the order record updates correctly.
- Monitor reconciliation for two weeks after launch, comparing provider callbacks against our database, because missed webhooks are the most common silent failure.
We also keep a simple reconciliation script — not a full accounting tool, just a daily check — that compares provider transaction lists against our order table by externalId. When the counts diverge, we investigate before the client notices.
What we recommend
For most Ugandan e-commerce builds, we recommend mobile money as the primary checkout, with card options available but not dominant. Use direct MTN MoMo and Airtel APIs where the merchant accounts exist, wrap them behind a unified backend layer, and compare Stripe and Flutterwave specifically for card settlements rather than defaulting to either. Test webhooks early with real endpoints, not just sandbox callbacks, and confirm settlement timing with actual bank partners before promising a customer anything about speed.
We do not build checkout flows that assume a global standard. We build them that assume a Ugandan buyer with a mobile-money wallet first, and we design the infrastructure to match.
Practical takeaway: Start with MTN MoMo and Airtel Money direct APIs, wrap them in a unified backend, add Stripe or Flutterwave for cards only after comparing local settlement terms, and always test webhooks against your production endpoint with a real transaction before launch.