Skip to main content

Production Python SDK for Safaricom M-Pesa Daraja v3

Project description

daraja-v3

Formerly mpesa-python — renamed to daraja-v3 on PyPI (the original name was taken).

Production Python SDK for Safaricom M-Pesa Daraja v3.

Tests Python License: MIT PyPI

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 — ValidationError catches 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 MpesaError broadly 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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

daraja_v3-0.1.0.tar.gz (13.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

daraja_v3-0.1.0-py3-none-any.whl (12.9 kB view details)

Uploaded Python 3

File details

Details for the file daraja_v3-0.1.0.tar.gz.

File metadata

  • Download URL: daraja_v3-0.1.0.tar.gz
  • Upload date:
  • Size: 13.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for daraja_v3-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3b1ead56f64b55b08a877fc54b54012a8de4913a4023e28694de8f26fb4255bc
MD5 d7d1b56bb1b90449ba397ed26b876ba2
BLAKE2b-256 03fcb4b15329ad0d17b607e2875111b600b27f7268c819d7e65a760ae3802042

See more details on using hashes here.

Provenance

The following attestation bundles were made for daraja_v3-0.1.0.tar.gz:

Publisher: publish.yml on gabrielmahia/mpesa-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file daraja_v3-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: daraja_v3-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 12.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for daraja_v3-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 110af4ad6caa8199b1f31c2b4041e99ec4d001ec0078b57d3dcf156b885c4947
MD5 1fed303bcc64c7be1a00e8aa7893d120
BLAKE2b-256 3b813beb21534da25136c7035c858e584440fd50a38cdd0fc1f5dc2fea9cba73

See more details on using hashes here.

Provenance

The following attestation bundles were made for daraja_v3-0.1.0-py3-none-any.whl:

Publisher: publish.yml on gabrielmahia/mpesa-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page