daraja-mock
A small local test double for the Safaricom M-Pesa Daraja API, built on Flask. It lets you exercise OAuth and STK Push flows (and the request shapes of a few other endpoints) without a Safaricom account, credentials or internet access.
What this is, and is not. A test double, not a simulator of Safaricom's behaviour, and not affiliated with Safaricom. Only the STK Push query result is configurable. B2C, Account Balance and Transaction Status always return an "accepted" response, and no asynchronous result callbacks are sent. See Limits.
Install
pip install daraja-mock # installs Flask
Quickstart
import requests
from daraja_mock import DarajaMock
mock = DarajaMock()
base_url = mock.run_thread(port=18080) # non-blocking; returns "http://127.0.0.1:18080"
token = requests.get(f"{base_url}/oauth/v1/generate").json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
push = requests.post(
f"{base_url}/mpesa/stkpush/v1/processrequest", headers=headers,
json={"BusinessShortCode": "174379", "Amount": 100, "PhoneNumber": "254712345678",
"CallBackURL": "https://example.com/callback", "AccountReference": "Order001", "TransactionDesc": "Payment"},
).json()
assert push["ResponseCode"] == "0"
mock.set_stk_result(1032) # STK queries now report "cancelled by user"
status = requests.post(f"{base_url}/mpesa/stkpushquery/v1/query", headers=headers,
json={"CheckoutRequestID": push["CheckoutRequestID"]}).json()
assert status["ResultCode"] == "1032"
Configuration
| Method | Effect |
|---|---|
set_stk_result(code) |
The ResultCode returned by every STK Push query. Described codes: 0 success, 1 insufficient balance, 1001 and 2001 invalid initiator, 1032 cancelled by user, 1037 timeout. Any other code is returned as Error <code>. |
reset() |
Restore defaults and clear the request log. |
request_log() |
List of recorded requests, each a dict with method, path, body and ts. |
set_b2c_result(...), set_balance(...) |
No effect yet (they warn). The B2C and balance endpoints always accept. |
The STK query endpoint returns the configured ResultCode for any CheckoutRequestID; it does not look the id up, and it returns a fresh random CheckoutRequestID of its own.
Endpoints
| Endpoint | Method | Behaviour |
|---|---|---|
/oauth/v1/generate |
GET | Returns access_token and expires_in; credentials are not checked. |
/mpesa/stkpush/v1/processrequest |
POST | Accepts the request; ResponseCode is "0". |
/mpesa/stkpushquery/v1/query |
POST | Returns the configured ResultCode. |
/mpesa/b2c/v3/paymentrequest |
POST | Always accepted. |
/mpesa/transactionstatus/v1/query |
POST | Always accepted. |
/mpesa/accountbalance/v1/query |
POST | Always accepted. |
/mock/balance-callback |
POST | Receives a balance result if you use it as your callback URL. |
/health |
GET | {"status": "ok", "version": ...} |
Standalone server
python -m daraja_mock # http://127.0.0.1:8765
python -m daraja_mock --port 9000
daraja-mock --port 9000 # the same, as an installed command
Use with pesa-cli
pesa-cli 1.0.1 and later reads PESA_BASE_URL, so its commands can run against this server:
python -m daraja_mock --port 8765 &
export PESA_BASE_URL=http://127.0.0.1:8765
pesa auth && pesa stk push 0712345678 100
mpesa-python does not currently accept a custom base URL, so it cannot be pointed at this server yet.
Limits
- Credentials, shortcodes, passkeys and callback URLs are not validated.
- No asynchronous result or callback delivery: STK, B2C and balance results that real Daraja posts to your URLs are not sent.
- STK query is stateless (see above). No C2B URL registration endpoint.
- It runs on Flask's development server, in a daemon thread for
run_thread, in one process.
Tests
pip install -e ".[dev]" && python -m pytest
The tests also fail if this README names a class, method or endpoint that does not exist, and they run the Quickstart above.
Part of the nairobi-stack East Africa engineering ecosystem. Maintained by Gabriel Mahia. Kenya × USA.
Metadata
Release files for daraja-mock 1.0.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| daraja_mock-1.0.4.tar.gz | 9.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| daraja_mock-1.0.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 17.3 kB
Release files / daraja_mock-1.0.4.tar.gz
| Download URL | daraja_mock-1.0.4.tar.gz |
|---|---|
| Size | 9.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
42776866b79a6a428ad4431b374a92b43a5613e8e4e169f30443b2090e7a9024
|
|
BLAKE2b-256 checksum How to use checksums |
249c8a0dda0f6a597827c5bd6ea8aaf86a75cff85a039e252ffebf57b682be5e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.
Transparency logRelease files / daraja_mock-1.0.4-py3-none-any.whl
| Download URL | daraja_mock-1.0.4-py3-none-any.whl |
|---|---|
| Size | 7.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
40820249ec637fca89f5cad3e9d2fe368ad0c1a1b41daa6cb6496a561afd9062
|
|
BLAKE2b-256 checksum How to use checksums |
1dcd7bd94bc954f6e123baaa049033526e2eb97d459003171914d6aef3b8816a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.
Transparency log