daraja-v3
Formerly
mpesa-python— renamed todaraja-v3on PyPI (the original name was taken).
Production Python SDK for Safaricom M-Pesa Daraja v3.
Zero external dependencies. Type-annotated. Stripe-quality error handling.
from mpesa import MpesaClient
client = MpesaClient(
consumer_key="your_key",
consumer_secret="your_secret",
shortcode="174379",
passkey="your_lnm_passkey",
sandbox=True, # flip to False when you go live
)
# Prompt customer phone to enter PIN
result = client.stk_push(
phone="0712345678", # any Kenyan format — normalised automatically
amount=500,
reference="Order-001",
callback_url="https://yourapp.com/mpesa/callback",
)
print(result.checkout_request_id)
Why this exists
Every Kenya fintech project needs M-Pesa. Most of them roll their own wrapper — usually a 200-line mpesa_utils.py with no tests, swallowed exceptions, and phone number bugs. This is the library those teams should be importing instead.
Design goals (borrowed from Stripe's Python SDK):
- Fail before the network call when possible —
ValidationErrorcatches bad inputs locally - Typed results — no
response["Body"]["stkCallback"]["CallbackMetadata"]["Item"][0]["Value"] - Token caching — never burn a request on an expired token
- Composable exceptions — catch
MpesaErrorbroadly or specific subclasses narrowly - Zero dependencies — drop into any environment without conflicts
Installation
pip install daraja-v3
Or from source:
git clone https://github.com/gabrielmahia/mpesa-python
cd mpesa-python
pip install -e ".[dev]"
Supported APIs
| API | Method | What it does |
|---|---|---|
| STK Push | client.stk_push() |
Prompt customer phone to pay |
| STK Query | client.stk_query() |
Check STK push status |
| B2C | client.b2c() |
Send money to customer wallet |
| C2B Register | client.c2b_register_urls() |
Register payment notification URLs |
| Account Balance | client.account_balance() |
Query your shortcode balance |
| Webhook parsing | MpesaClient.parse_stk_callback() |
Parse Safaricom's webhook body |
Roadmap: Reversal, Transaction Status, QR Code, Ratiba (recurring payments)
Usage guide
STK Push + webhook
# 1. Initiate push
result = client.stk_push(
phone="0712345678",
amount=1500,
reference="Invoice-2024-042",
description="Subscription",
callback_url="https://yourapp.com/mpesa/stk/callback",
)
# 2. Store result.checkout_request_id against your order
# 3. In your webhook handler:
from mpesa import MpesaClient
def handle_stk_callback(request_body: dict):
payment = MpesaClient.parse_stk_callback(request_body)
if payment["paid"]:
receipt = payment["mpesa_receipt"] # e.g. "NLJ7RT61SV"
amount = payment["amount"] # e.g. 1500
phone = payment["phone"] # e.g. "254712345678"
# update your order, send confirmation email, etc.
B2C disbursement
result = client.b2c(
phone="0712345678",
amount=5000,
remarks="Monthly stipend",
initiator_name="testapi", # from your Daraja portal
security_credential="encrypted_cred",
callback_url="https://yourapp.com/mpesa/b2c/result",
queue_timeout_url="https://yourapp.com/mpesa/b2c/timeout",
)
print(result.conversation_id) # use to reconcile
Error handling
from mpesa import MpesaClient
from mpesa.exceptions import ValidationError, AuthenticationError, TransactionError, MpesaError
try:
result = client.stk_push(phone="bad-phone", amount=100, reference="ref")
except ValidationError as e:
# Bad input — no network call was made
print(f"Fix your input: {e.message} (code: {e.code})")
except AuthenticationError as e:
# Check your CONSUMER_KEY and CONSUMER_SECRET
print(f"Auth failed: {e.message}")
except TransactionError as e:
# M-Pesa returned a non-zero result (user cancelled, insufficient funds, etc.)
print(f"Transaction failed: {e.message} (M-Pesa code: {e.code})")
print(f"Raw response: {e.raw}")
except MpesaError as e:
# Catch-all for any other SDK error
print(f"Error: {e.message}")
Phone number normalisation
The SDK accepts any Kenyan phone format and normalises to 2547XXXXXXXX:
from mpesa.validators import phone
phone("0712345678") # → "254712345678"
phone("+254712345678") # → "254712345678"
phone("254712345678") # → "254712345678"
phone("712345678") # → "254712345678"
phone("bad-number") # → raises ValidationError
Development
# Clone and install dev dependencies
git clone https://github.com/gabrielmahia/mpesa-python
cd mpesa-python
pip install -e ".[dev]"
# Run tests
pytest
# Lint
ruff check .
# Type check
mypy mpesa/
Running integration tests
Integration tests require Daraja sandbox credentials. Set environment variables:
export MPESA_CONSUMER_KEY="your_sandbox_key"
export MPESA_CONSUMER_SECRET="your_sandbox_secret"
export MPESA_SHORTCODE="174379"
export MPESA_PASSKEY="your_sandbox_passkey"
export MPESA_CALLBACK_URL="https://your-ngrok-tunnel.ngrok.io/mpesa/callback"
pytest tests/integration/ -v
Get sandbox credentials free at developer.safaricom.co.ke.
Contributing
Issues and PRs welcome. See CONTRIBUTING.md.
This SDK follows the project conventions in nairobi-stack — the engineering guide for building products in East Africa.
License
MIT — use freely in commercial and open source projects. Attribution appreciated.
Built by a Kenyan engineer who has written this wrapper from scratch one too many times.
Release files for daraja-v3 0.1.0
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_v3-0.1.0.tar.gz | 13.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| daraja_v3-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:25.9 kB
Release files / daraja_v3-0.1.0.tar.gz
| Download URL | daraja_v3-0.1.0.tar.gz |
|---|---|
| Size | 13.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3b1ead56f64b55b08a877fc54b54012a8de4913a4023e28694de8f26fb4255bc
|
|
BLAKE2b-256 checksum How to use checksums |
03fcb4b15329ad0d17b607e2875111b600b27f7268c819d7e65a760ae3802042
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Mar 18, 2026.
Transparency logRelease files / daraja_v3-0.1.0-py3-none-any.whl
| Download URL | daraja_v3-0.1.0-py3-none-any.whl |
|---|---|
| Size | 12.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
110af4ad6caa8199b1f31c2b4041e99ec4d001ec0078b57d3dcf156b885c4947
|
|
BLAKE2b-256 checksum How to use checksums |
3b813beb21534da25136c7035c858e584440fd50a38cdd0fc1f5dc2fea9cba73
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Mar 18, 2026.
Transparency log