Insights / Payment Engineering

Building Secure Payment Integrations

M-Pesa done right: verification, reconciliation and idempotent processing.

  • Payment Security
  • September 2026
  • 8 min read
  • M-Pesa
  • Payment Security
  • API Security
  • Secure Engineering

A payment isn't confirmed just because a callback arrived

Most Kenyan business software touches M-Pesa somewhere — and most payment bugs we see are not exotic attacks but missing controls around ordinary flows: callbacks trusted blindly, retries charged twice, reconciliations done by hand at month end. These are the controls we apply when designing payment workflows for business software, including our Simba Retail platform.

The first principle follows from that model: a callback is a notification, not a verdict. Treat it as untrusted input, and let verification and reconciliation decide the final state.

The six controls at a glance

In the order we implement them. Select any control to jump to it.

  1. C1Verify every transaction
  2. C2Reconcile continuously
  3. C3Make processing idempotent
  4. C4Keep secrets server-side
  5. C5Roles and audit trails
  6. C6Handle unknown states
C1

Verify every transaction, never trust the callback alone

A payment callback is just an HTTP request — anyone can send one. Record the callback, then confirm the transaction independently (query the transaction status API) before crediting a customer account or releasing goods. Only state transitions confirmed by verification should move money in your books.

The risk

A request arrives claiming success, the order is marked paid and goods are released — but the money was never actually collected.

The control

Record first, verify independently, then transition. A callback is a hint that verification is due, not permission to move money.

C2

Reconcile continuously, not monthly

Match every internal payment record against the provider's transaction references on a short cycle. Unmatched items — callbacks received but never recorded, records with no matching callback — surface the same day, while logs and staff memory are fresh.

The risk

Reconciliation deferred to a month-end spreadsheet, by which point the log lines and staff recollections needed to explain a gap are gone.

The control

Run the match on a short cycle and route every unmatched item to a named owner the same day it appears.

C3

Make processing idempotent

Networks retry, users double-tap, callbacks arrive twice. Design payment handlers so repeating the same request has the same effect as running it once: unique transaction references enforced at the database level, and duplicate deliveries acknowledged without re-applying their effects. This is the control that prevents double charges.

The risk

A handler that applies whatever effect each delivery describes, so a retried request processes the same payment a second time.

The control

Enforce uniqueness on the transaction reference in the database itself rather than in application code, and acknowledge duplicates without re-applying effects.

C4

Keep secrets server-side with short-lived tokens

API credentials belong on the server, never in mobile apps or front-end bundles. Request short-lived access tokens, rotate credentials on a schedule, and scope every credential to the minimum it needs.

The risk

An integration key shipped inside a JavaScript bundle or a mobile app, where anyone who unzips the artefact holds your credential.

The control

Hold credentials on the server, request a short-lived token per operation, rotate on a schedule, and scope each credential to the minimum it needs.

C5

Put payments behind roles and audit trails

Refunds, voids and manual adjustments are the highest-risk actions in any commerce system. Restrict them to named roles and log who did what, when, and for how much — the same controls we ship in Simba Retail.

The risk

Every staff account can void a sale or issue a refund, and the only record of it is a line in a general application log.

The control

Map roles to capabilities explicitly — take a payment, issue a refund and change configuration are three different permissions — and write an audit entry for each privileged action.

C6

Handle timeouts and offline paths explicitly

In markets where connectivity drops, “unknown payment state” is a normal outcome, not an edge case. Queues with explicit pending states, safe retries and clear cashier-facing messaging keep sales moving without risking double collection.

The risk

A binary success-or-failure model, so a dropped connection leaves the sale in a state the system cannot represent — and staff resolve it by taking the money again.

The control

Model pending as a first-class state, retry safely under the same idempotency rules, and tell the cashier what is happening instead of asking them to guess.

Scope of these recommendations

The control patterns above are Intelisav's recommended design for payment integrations. They are not statements of what a payment provider guarantees. For M-Pesa, the endpoints, request and response fields, callback delivery behaviour and any signature scheme are defined by Safaricom and can change over time.

Confirm current behaviour against the official Safaricom Daraja developer documentation before implementing, and treat verification and reconciliation as the controls that hold regardless of what the provider does or does not promise.

—

Secure payment architecture

Secure payment processing is not one control. It is a chain of controls: verification, idempotency and audit each guard a different failure, and reconciliation is what proves the chain held.

Building a payment integration?

We help organizations design payment flows with verification, reconciliation and safe retry handling — and assess existing integrations against them.

Request a Security Assessment