Skip to main content

mpesakit

⚡ Effortless M-Pesa integration using Safaricom's Daraja API — built for developers, by developers.

Python 3.12+ License: Apache 2.0 PyPI version Downloads Build Status


Integrating Safaricom's Daraja API from scratch means wrestling with OAuth2 token rotation, security credential encryption, inconsistent sandbox vs. production endpoints, and documentation that rarely connects end-to-end.

mpesakit handles all of that. Add your credentials, call a method, move on.


Installation

pip install mpesakit

Quick Start

1. Set your credentials

export MPESA_CONSUMER_KEY="your_consumer_key"
export MPESA_CONSUMER_SECRET="your_consumer_secret"
export MPESA_SHORTCODE="your_shortcode"
export MPESA_PASSKEY="your_lipa_na_mpesa_passkey"
export MPESA_PHONE_NUMBER="254712345678"

2. Trigger an STK Push

import os
from dotenv import load_dotenv
from mpesakit import MpesaClient
from mpesakit.mpesa_express import TransactionType

load_dotenv()

client = MpesaClient(
    consumer_key=os.getenv("MPESA_CONSUMER_KEY"),
    consumer_secret=os.getenv("MPESA_CONSUMER_SECRET"),
    environment="sandbox",  # Switch to "production" when ready
)

response = client.stk_push(
    business_short_code=int(os.getenv("MPESA_SHORTCODE")),
    passkey=os.getenv("MPESA_PASSKEY"),
    transaction_type=TransactionType.CUSTOMER_PAYBILL_ONLINE,
    amount=250,
    party_a=os.getenv("MPESA_PHONE_NUMBER"),
    party_b=os.getenv("MPESA_SHORTCODE"),
    phone_number=os.getenv("MPESA_PHONE_NUMBER"),
    callback_url="https://yourdomain.com/mpesa/callback",
    account_reference="Order-001",
    transaction_desc="Payment for order",
)

if response.is_successful():
    print("Request accepted:", response.CheckoutRequestID)
else:
    print("Error:", response.error_message())

3. Handle the payment callback

The client exposes process_* methods that validate and deserialize incoming Safaricom payloads into typed Pydantic objects — no manual dict parsing required.

# Example: FastAPI callback handler
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.post("/mpesa/callback")
async def mpesa_callback(request: Request):
    payload = await request.json()

    # Validates the payload and returns a typed StkPushSimulateCallback object
    callback = client.process_stk_callback(payload)

    if callback.is_successful()
        metadata = callback.Body.stkCallback.CallbackMetadata
        print(f"Payment confirmed — Receipt: {metadata}")
    else:
        print(f"Payment failed: {callback.Body.stkCallback.ResultDesc}")

    return JSONResponse({"ResultCode": 0, "ResultDesc": "Accepted"})

All process_* methods follow the same pattern — pass in the raw JSON payload, get back a validated object:

Method Returns
client.process_stk_callback(payload) StkPushSimulateCallback
client.process_stk_query_callback(payload) StkPushQueryResponse
client.process_b2c_callback(payload) B2CResultCallback
client.process_account_balance_callback(payload) AccountBalanceResultCallback
client.process_account_balance_timeout(payload) AccountBalanceTimeoutCallback
client.process_transcations_callback(payload) TransactionStatusResultCallback
client.process_reversal_callback(payload) ReversalResultCallback
client.process_tax_remittance_callback(payload) TaxRemittanceResultCallback
client.process_dynamic_qr_code_callback(payload) DynamicQRGenerateResponse
client.process_b2b_callback(payload) B2BExpressCheckoutCallback
client.process_bill_manager_callback(payload) BillManagerPaymentNotificationRequest
client.process_ratiba_service_callback(payload) StandingOrderCallback

4. Error handling

from mpesakit.errors import MpesaApiException

try:
    response = client.stk_push(...)
except MpesaApiException as e:
    err = e.error
    print(f"Code: {err.error_code}")       # e.g. AUTH_INVALID_CREDENTIALS
    print(f"Message: {err.error_message}") # Human-readable description
    print(f"HTTP status: {err.status_code}")
    print(f"Request ID: {err.request_id}")
except Exception as exc:
    print(f"Unexpected error: {exc}")

More Examples

B2C — Send money to a customer

from mpesakit.b2c import B2CCommandIDType

response = client.b2c.send_payment(
    originator_conversation_id="ocid-1234-5678",
    initiator_name="your_initiator_name",
    security_credential="your_encrypted_security_credential",
    command_id=B2CCommandIDType.BusinessPayment,
    amount=1500,
    party_a="600999",           # Your bulk disbursement shortcode
    party_b="254712345678",     # Recipient phone number (normalized by SDK)
    remarks="Refund for order 042",
    queue_timeout_url="https://yourdomain.com/mpesa/timeout",
    result_url="https://yourdomain.com/mpesa/result",
)

if response.is_successful():
    print("Payout sent:", response.ResponseDescription)

Note: B2C in production requires a Bulk Disbursement Account from Safaricom — a standard PayBill or Till will not work. See the B2C docs for details.

STK Query — Check a push status

response = client.stk_query(
    business_short_code=int(os.getenv("MPESA_SHORTCODE")),
    passkey=os.getenv("MPESA_PASSKEY"),
    checkout_request_id="ws_CO_191220191020363925",
)

Switching to production

client = MpesaClient(
    consumer_key="...",
    consumer_secret="...",
    environment="production",  # That's all it takes
)

Supported APIs

API Status Description
STK Push ✅ Ready Prompt a customer to enter their M-Pesa PIN to pay
STK Query ✅ Ready Check the status of an STK Push request
C2B Payments ✅ Ready Receive payments from customers via paybill or till
B2C Payments ✅ Ready Send money to customers or staff
B2C Account Top-up ✅ Ready Top up B2C utility accounts
Business Paybill ✅ Ready Business-to-business paybill transfers
Business BuyGoods ✅ Ready Business-to-business till transfers
Token Management ✅ Ready Automatic OAuth2 token handling — no manual refresh needed
Account Balance ✅ Ready Query your M-Pesa account balance
Transaction Status ✅ Ready Look up the status of any past transaction
Transaction Reversal ✅ Ready Reverse erroneous transactions
Dynamic QR ✅ Ready Generate QR codes for M-Pesa payments
Tax Remittance ✅ Ready Submit tax remittances via M-Pesa

Security Best Practices

  • Never commit credentials to version control — use environment variables or a secrets manager
  • Validate callbacks using is_mpesa_ip_allowed to restrict requests to known Safaricom IP ranges
  • Use HTTPS for all callback URLs — Safaricom will not deliver to plain HTTP in production
  • Log transaction IDs (OriginatorConversationID, ConversationID) for reconciliation and dispute resolution
  • Persist callback payloads before returning an acknowledgement, to protect against processing failures

Full Documentation

API reference, webhook guides, and production checklist: mpesakit.dev


Contributing

git clone https://github.com/Byte-Barn/mpesakit.git
cd mpesakit

python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

pip install -e ".[dev]"
pytest tests/unit

Ways to contribute:

Please follow PEP 8 and include type hints in new code.


Support


License

Apache 2.0 — free for commercial and private use.


Made with ❤️ for the Kenyan developer community

⭐ Star this repo · 🐛 Report a bug · 💡 Request a feature

Built on the shoulders of Arlus/mpesa-py

Metadata

Release files for mpesakit 2.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mpesakit 2.2.0
File Size Uploaded
mpesakit-2.2.0.tar.gz 60.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mpesakit 2.2.0
File Interpreter ABI Platform
mpesakit-2.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 156.2 kB

Release files / mpesakit-2.2.0.tar.gz

Download URL mpesakit-2.2.0.tar.gz
Size 60.7 kB
Tags Source
SHA-256 checksum
How to use checksums
69134b86b92a1f49834842abbe97498fa969a6821fb4f55dbd857e981ec4b1ce
BLAKE2b-256 checksum
How to use checksums
8b5d9286beb4d1b7a3e09e01f4995e9784ff7a75293569b2d4f72097f99aa6c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via Hatch/1.17.1 {"ci":true,"cpu":"x86_64","distro":{"id":"noble","libc":{"lib":"glibc","version":"2.39"},"name":"Ubuntu","version":"24.04"},"implementation":{"name":"CPython","version":"3.12.13"},"installer":{"name":"hatch","version":"1.17.1"},"openssl_version":"OpenSSL 3.0.13 30 Jan 2024","python":"3.12.13","system":{"name":"Linux","release":"6.17.0-1020-azure"}} HTTPX2/2.9.1

Release files / mpesakit-2.2.0-py3-none-any.whl

Download URL mpesakit-2.2.0-py3-none-any.whl
Size 95.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f729672d0f5206fe43d0422cf1d3926e63e4345b9992a0f94d93f0588ba3a356
BLAKE2b-256 checksum
How to use checksums
e2c723b103de972f4c9b0123f01a83e6a1d21cf8d570303b42d21a9c23a09b04
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via Hatch/1.17.1 {"ci":true,"cpu":"x86_64","distro":{"id":"noble","libc":{"lib":"glibc","version":"2.39"},"name":"Ubuntu","version":"24.04"},"implementation":{"name":"CPython","version":"3.12.13"},"installer":{"name":"hatch","version":"1.17.1"},"openssl_version":"OpenSSL 3.0.13 30 Jan 2024","python":"3.12.13","system":{"name":"Linux","release":"6.17.0-1020-azure"}} HTTPX2/2.9.1

Release history Release notifications | RSS feed

This release

2.2.0 This release

2 release files

2.1.1

2 release files

2.1.0

2 release files

2.0.5

2 release files

2.0.4

2 release files

2.0.3

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.1.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page