# How M-Pesa settlement survives missing callbacks

Canonical: https://stevenmoriasi.com/blog/daraja-mpesa-for-woocommerce
By [Steven Moriasi](https://www.linkedin.com/in/steven-moriasi/).
Source: https://github.com/steven-moriasi/daraja-mpesa-for-woocommerce
Date: 2026-09-04

## Context

Closing a tab, or trusting a callback body, is not settlement. M-Pesa Express looks simple from checkout. The failure modes are missing callbacks, duplicate receipts, and amounts that do not match.

[Daraja M-Pesa for WooCommerce](https://github.com/steven-moriasi/daraja-mpesa-for-woocommerce) is the PHP boundary because that is where WooCommerce lives. The invariant is the same as the Python and .NET labs: money moves after evidence matches.

## Decision

STK Push starts only after an immutable payment attempt exists. The WooCommerce order stays unpaid until a callback, or a correlated status query, supplies the exact amount and a unique receipt.

attempt row → STK Push → callback and/or STK Query → exact-amount match → paid

The storefront never calls Safaricom. Polling on the order page is read-only. If the customer leaves, server-side reconciliation continues. STK Query success without an exact callback amount becomes `succeeded_unverified`; it does not mark the order paid. Manual verification is capability-protected, nonce-protected, and requires a fresh Daraja query plus an operator reason.

## What I rejected

Marking the order paid from the browser return URL, and treating any Daraja “success” as settlement without the amount. Both create paid orders with no matching receipt.

## Failure scenario

The callback never arrives. Reconciliation runs STK Query. Amount mismatch stays unpaid. Duplicate callbacks with the same receipt are idempotent. Tokens stay out of logs; phone numbers are a keyed hash on the attempt.

## Evidence

WordPress/WooCommerce plugin, callback correlation tests, and documented timeout plus manual verification. Independent work, not affiliated with Safaricom or WooCommerce. Pre-release; use sandbox credentials until callbacks, reconciliation, and refunds are rehearsed.

Inspect: [Repository](https://github.com/steven-moriasi/daraja-mpesa-for-woocommerce) · [Run locally](https://github.com/steven-moriasi/daraja-mpesa-for-woocommerce#readme)

_—by [Steven Moriasi](https://www.linkedin.com/in/steven-moriasi/)_
