When I started building HomeManager, a property management platform for Kenyan landlords, I knew that integrating with M-Pesa wasn't just a nice-to-have feature—it was absolutely essential. With over 50 million M-Pesa transactions happening daily in Kenya, any fintech solution that ignores this payment method is essentially ignoring its entire market.
In this post, I'll share the technical challenges, architecture decisions, and lessons learned from building a property management system with M-Pesa at its core.
The Problem We're Solving
Most Kenyan landlords still manage their properties using spreadsheets, WhatsApp groups, and paper receipts. This creates several pain points:
- No automated rent tracking - Landlords manually reconcile M-Pesa messages with expected payments
- Poor communication - Tenants miss payment reminders, landlords forget maintenance requests
- No financial visibility - Understanding property performance requires hours of manual calculation
Architecture Overview
HomeManager is built with Python (FastAPI) on the backend, with a React Native mobile app for tenants and a web dashboard for landlords.
Integrating M-Pesa: The Daraja API
Safaricom provides the Daraja API for M-Pesa integration. Here's what you need to know:
1. Getting Started
First, register for a developer account at developer.safaricom.co.ke. You'll get sandbox credentials for testing.
2. STK Push Implementation
The STK (SIM Toolkit) Push initiates a payment request directly to the tenant's phone:
import httpx
import base64
from datetime import datetime
class MpesaClient:
def __init__(self, consumer_key: str, consumer_secret: str, shortcode: str):
self.consumer_key = consumer_key
self.consumer_secret = consumer_secret
self.shortcode = shortcode
self.base_url = "https://sandbox.safaricom.co.ke"
async def get_access_token(self) -> str:
credentials = base64.b64encode(
f"{self.consumer_key}:{self.consumer_secret}".encode()
).decode()
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/oauth/v1/generate?grant_type=client_credentials",
headers={"Authorization": f"Basic {credentials}"}
)
return response.json()["access_token"]
async def stk_push(self, phone_number: str, amount: int, account_ref: str, desc: str) -> dict:
access_token = await self.get_access_token()
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
payload = {
"BusinessShortCode": self.shortcode,
"Timestamp": timestamp,
"TransactionType": "CustomerPayBillOnline",
"Amount": amount,
"PartyA": phone_number,
"PartyB": self.shortcode,
"PhoneNumber": phone_number,
"CallBackURL": "https://your-domain.com/api/mpesa/callback",
"AccountReference": account_ref,
"TransactionDesc": desc
}
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/mpesa/stkpush/v1/processrequest",
json=payload,
headers={"Authorization": f"Bearer {access_token}"}
)
return response.json()
3. Handling Callbacks
M-Pesa sends payment confirmations to your callback URL:
from fastapi import APIRouter, Request
router = APIRouter()
@router.post("/api/mpesa/callback")
async def mpesa_callback(request: Request):
data = await request.json()
result_code = data["Body"]["stkCallback"]["ResultCode"]
if result_code == 0:
callback_metadata = data["Body"]["stkCallback"]["CallbackMetadata"]["Item"]
amount = next(item["Value"] for item in callback_metadata if item["Name"] == "Amount")
mpesa_receipt = next(item["Value"] for item in callback_metadata if item["Name"] == "MpesaReceiptNumber")
# Update payment record in database
await update_payment_status(mpesa_receipt=mpesa_receipt, amount=amount, status="completed")
return {"ResultCode": 0, "ResultDesc": "Accepted"}
Key Lessons Learned
1. Always Use Idempotency Keys
M-Pesa callbacks can be duplicated. Always use the MpesaReceiptNumber as an idempotency key to prevent double-processing payments.
2. Handle Network Timeouts Gracefully
Safaricom's API can be slow during peak hours. Set appropriate timeouts (30+ seconds) and implement retry logic with exponential backoff.
3. Test with Real Money (Small Amounts)
Sandbox testing only gets you so far. The production environment behaves differently. Test with KES 1-10 payments before going live.
4. Implement Proper Logging
When payments fail, you need visibility. Log everything—but mask sensitive data like full phone numbers.
What's Next for HomeManager
We're currently rolling out to landlords in Nairobi with plans to expand across Kenya. Features we're building:
- Automated rent reminders via SMS and WhatsApp
- Utility bill splitting for shared apartments
- Maintenance request tracking with photo uploads
- Financial reports for tax purposes
Try It Out
If you're a landlord in Kenya looking to simplify your property management, check out HomeManager at buniva.co.ke. We offer a free tier for landlords with up to 5 units.
For developers interested in M-Pesa integration, the official documentation at developer.safaricom.co.ke is a great starting point.
Have questions about M-Pesa integration or building SaaS products for the African market? Drop them in the comments below!
Follow me for more content about building tech products for emerging markets.
Top comments (0)