if you have ever typed "mpesa daraja django" into a github search bar, you know the feeling: pages of repos, half a dozen packages with nearly identical names, and no obvious answer to the only question that matters, which one should i actually use? i finally sat down(not literally) and answered it properly. i took a spreadsheet(Numbers) of 209 M-Pesa Daraja projects (Python and Django focused), deduplicated it to 191 unique projects, cloned every reachable GitHub repository (163 of them), pulled download numbers for all 47 PyPI packages, and ran static analysis on the source code of each one.

this article is what i found: why almost none of these projects are widely used, why exactly one of them is, and whether the approaches they take to integrating Daraja in Django make sense.

Daraja is Safaricom's REST API for M-Pesa: STK Push (Lipa na M-Pesa Online), C2B (customer to business), B2C (business to customer), transaction status, reversals, and friends. if you are building anything that takes money in Kenya, you will meet it.

how i did this

quick methodology note so you can judge the numbers for yourself:

  • the source list is a spreadsheet of 209 records (162 GitHub repositories, 47 PyPI packages) collected from GitHub and PyPI searches for Python, Django, M-PESA, Daraja and STK Push. after merging duplicates (the same project listed as both a repo and a package), that is 191 unique projects.
  • i had an agent(cowork) shallow-cloned all 163 unique repositories. 160 succeeded; 3 no longer exist on GitHub.
  • for each clone i extracted the last commit date, file inventory, and ran pattern analysis on the Python source: which HTTP client it uses, how it handles OAuth tokens, whether credentials are hardcoded, whether callbacks are handled, whether tests exist.
  • download counts come from pypistats.org, stars and forks from GitHub, all as of August 4, 2026.

everything quantitative below comes from that pipeline, and the full per-project catalog is in the appendix at the bottom.

the headline numbers

let me start with the graveyard chart.

Bar chart: year of last commit across 160 reachable GitHub repos

of the 160 repositories i could clone, 114 have not seen a commit in over two years. only 18 projects had any activity in the last six months, and several of those are bot-refreshed mirrors rather than actual development. three repositories from the list have been deleted from GitHub entirely (yourdudeken/mpesa-sdk, TralahM/django-mpesa and Developer-Felix/mpesa-payments-package), which tells you something about depending on this ecosystem: links rot.

next, what the projects actually are.

Bar chart: what the 191 unique projects actually are

a few things jump out:

  • 38 of the repositories are forks or mirrors of django-daraja. i checksummed the core engine file (django_daraja/mpesa/core.py) across them: 37 of the 38 carry a byte-identical copy of the original (the last one vendors the app with the engine file moved). every one of them is the same library wearing a different jersey.
  • 55 projects are DIY integrations: a Django (or Flask/FastAPI) app making raw HTTP calls to Daraja endpoints with requests, usually straight from views.py.
  • only about 24 projects are actual reusable libraries or SDKs, and most of those are single-maintainer efforts that stopped moving years ago.
  • 28 PyPI packages have no public repository at all. you would be installing payment-handling code you cannot read the history of. please do not.

and finally, usage. this is the chart that answers "why are they not widely used" in one picture.

Bar chart: top 10 M-Pesa Daraja Python packages by monthly PyPI downloads

the most downloaded M-Pesa package in the Python world, django-daraja, gets about 1,041 downloads a month. for context, stripe on PyPI does millions a month. the entire Python M-Pesa ecosystem combined moves fewer downloads in a month than a mid-tier utility library does in an afternoon. number two on the list, interestingly, is mpesa-mcp, a Model Context Protocol (MCP) server that lets AI agents trigger M-Pesa payments. more on that later.

why almost nothing here is widely used

after reading through this much code, i think the low adoption comes down to five reasons, and none of them is "the code is bad" (though some of it is).

1. the ecosystem is fragmented beyond reason

47 packages, and the names are a minefield: mpesa-py, mpesapy, pympesa, python-mpesa, python-daraja, pydaraja, daraja, daraja-py, daraja-mpesa, mpesa-daraja, python-mpesa-daraja, py-mpesa-daraja-api.

i am not making any of these up; every one is a real, distinct package. a developer searching PyPI has no signal for which of these is maintained, which is a student project, and which is an abandoned upload from 2018. so most developers pick none of them and write the calls themselves, which produces another tutorial repo, which makes the search results worse. the flywheel spins backwards.

GitHub search results for mpesa daraja django

2. the API is small enough that wrappers feel optional

here is the uncomfortable truth about Daraja client libraries: the API surface they wrap is tiny. authentication is one GET request with basic auth that returns a bearer token. STK Push is one JSON POST with a base64-encoded password.

a competent developer can write both in 40 lines. so a thin wrapper whose value proposition is "we did the base64 for you" does not clear the bar for taking on a dependency, especially a dependency with one maintainer and no releases since 2022.

the actually hard parts of M-Pesa integration (callbacks, idempotency, reconciliation, going live) are exactly the parts most wrappers do not touch.

3. it is a tutorial-driven ecosystem

the majority of the 160 repos are learning artifacts: class projects, portfolio pieces, blog-post companions, YouTube tutorial code.

no shade to anyone learning in public (that is how i learned too), but the data is blunt. of the 62 standalone Django projects (excluding the django-daraja mirrors):

  • 38 commit their db.sqlite3 to git
  • 34 hardcode the shared Daraja sandbox passkey in source
  • 32 have consumer keys or secrets as string literals in the code
  • 21 slap @csrf_exempt on the callback view and perform no validation of what hits it
  • 18 reference ngrok in code or docs(maybe not a bad thing)
  • only 11 cache the OAuth token at all (the rest request a fresh token for every API call)
  • only 10 even mention the production URL (api.safaricom.co.ke); the rest are sandbox-only forever
  • only 5 have real tests
  • and 9 repos ship an entire committed virtualenv, site-packages and all

these projects were never meant to be used by anyone but their author, for anything but a demo. they are the ecosystem's noise floor, and there is a lot of it.

kalutu/mpesa-daraja-api tutorial repository on GitHub

4. money code has a trust bar most repos cannot clear

payments are the one domain where developers rightly refuse to depend on a stranger's abandoned side project. an 80-star library with no CI, no releases in three years, and one maintainer is an easy "no" when the code moves customer money.

so what do Kenyan companies actually do? they write their own private, in-house Daraja module, battle-test it in production, and never open-source it. the public ecosystem stays starved of exactly the production-hardened code that would make it trustworthy. (the recently released mainfinity-django-mpesa, extracted from a production system, is a rare counterexample, and i will get to it.)

5. Safaricom itself checked out in 2018

this one deserves its own screenshot. safaricom's mpesa-py project.

safaricom/mpesa-py official repo, last commit 2018

the Python SDK sitting in Safaricom's official GitHub org, safaricom/mpesa-py, is a copy of a community wrapper (Arlus/mpesa-py), it still describes itself as "unofficial" in the README, and its last commit was July 2018. when the platform owner will not maintain a Python SDK, every community library carries the entire maintenance burden alone, and the API's quirks (changing portal, certificate handling, inconsistent error formats) land on volunteers. compare this with Stripe or Paystack, whose official SDKs are the reason nobody needs 47 community wrappers.

the one that won: django-daraja

martinmogusu/django-daraja is the de facto standard, and it is not close: 80 stars, 85 forks (forks outnumbering stars is the tutorial-ecosystem tell), roughly 1,041 PyPI downloads a month, and 38 of the projects in this dataset are literally just copies of it.

martinmogusu/django-daraja repository on GitHub

how it works

the design is a single MpesaClient class you instantiate anywhere in your Django code:

from django_daraja.mpesa.core import MpesaClient

client = MpesaClient()
response = client.stk_push(
    phone_number, amount, account_reference,
    transaction_desc, callback_url,
)

under the hood:

  • configuration comes from Django settings or a .env file via python-decouple (MPESA_ENVIRONMENT, MPESA_CONSUMER_KEY, and so on), so credentials stay out of code. good.
  • the OAuth token is cached in the database. there is an AccessToken model, and on each call the library checks whether the stored token is older than 50 minutes before requesting a new one. workable, and honestly smarter than the 90 percent of tutorial repos that fetch a token per request.
  • responses are wrapped by literally reassigning the class of the requests.Response object (r.__class__ = MpesaResponse). it works, and it made me smile, but it is the kind of trick you write once and explain forever.
  • B2C security credentials are encrypted properly with the cryptography package and Safaricom's public certificate, which most DIY projects never implement at all.

why it won

it was first (2018), it has actual documentation with a readthedocs site, the README walks you from install to a successful sandbox STK push in minutes, and it does the one thing 95 percent of people need (STK push) with the least ceremony. adoption compounds: every tutorial that uses it produces more search results pointing at it.

django-daraja PyPI download stats showing 1041 downloads last month

why you should still read the code before trusting it

  • the last release (v1.3.0) was 2023, and the last commit was December 2023.
  • install_requires says Django>=1.11 with no upper bound, which is generous to a fault; nothing guarantees compatibility with Django 5.x beyond luck and simplicity.
  • the token cache has a race: it deletes all AccessToken rows then creates a new one, with no locking, so concurrent requests can collide. the token column is also max_length=30, which fits today's tokens with almost no headroom.
  • there is no callback-side tooling at all: no models for transactions, no idempotency handling for Safaricom's retries, no validation of incoming callbacks. the half of the integration that actually hurts is left entirely to you.
  • it is synchronous only, and open issues sit unanswered.

so: excellent for a sandbox demo or a low-traffic paybill, and i understand completely why it is the default recommendation. it is carrying an ecosystem on a 2023 codebase.

the copy-paste pattern (and why it keeps happening)

the second most popular "approach" skips libraries entirely: the tutorial pattern, a Django view that builds the STK push request inline. the most starred Python Daraja repo after django-daraja is exactly this, a Flask STK push walkthrough with 79 stars last touched in 2021.

stangatimu STK push Flask tutorial repository

the canonical shape, which i found in 55 repositories with only cosmetic variation:

# views.py, the pattern (do not copy this)
consumer_key = "GTq7DphRJmDOnLuTx2j..."   # hardcoded
consumer_secret = "amFbAoUByPV1yM0V..."   # hardcoded

def get_access_token(request):
    r = requests.get(api_url, auth=HTTPBasicAuth(consumer_key, consumer_secret))
    return r.json()['access_token']       # fetched fresh on every call

@csrf_exempt
def callback(request):
    data = json.loads(request.body)       # no validation, no idempotency
    # ... write to db, maybe

does this make sense? as a learning exercise, absolutely: Daraja is a great first "real API" precisely because it is small, and writing the OAuth dance yourself once is genuinely educational.

as anything more than that, no. the pattern teaches four habits that hurt in production: secrets in source, a token request per payment, an unauthenticated callback endpoint that trusts anything POSTed to it, and zero story for what happens when Safaricom retries a callback or never sends one. and because these tutorials end at the sandbox success screen, only 10 of the 62 standalone Django projects in the dataset even reference the production URL. the tutorial ecosystem is, in a very literal sense, sandbox-only.

the second generation (2024 to 2026) is better

the encouraging part of this research: the newest projects have clearly learned from the last eight years. four are worth your attention.

mpesakit: the modern SDK

mpesakit (first released August 2025, actively developed, Apache-2.0) is what a 2026 Python SDK should look like: Pydantic models for every request and response, both sync and fully async clients, coverage of the whole Daraja surface (STK, C2B, B2C, B2B express, dynamic QR, tax remittance, bill manager, ratiba), automatic token management, and 50 test files in a 125-file codebase. it is framework-agnostic, so it fits FastAPI as naturally as Django. at 185 downloads a month it has not "won" anything yet, but it is the strongest engineering in the entire dataset.

Byte-Barn/mpesakit repository on GitHub

mainfinity-django-mpesa: the production-shaped Django app

mainfinity-django-mpesa (July 2026) is the rare package extracted from a running production system rather than written for a tutorial. it ships the things every DIY project skips: idempotent callback handling (Safaricom retries are settled exactly once), an IP allowlist middleware for callback endpoints, transaction status queries for reconciliation, CI with a coverage badge, and explicit Django 4.2/5.1 support. it is weeks old and has 41 downloads a month, so the trust bar from reason 4 applies to it too, but this is the most production-minded Django-specific package in the list.

Wachiradanito/django-mpesa (mainfinity-django-mpesa) repository

pesa-playground and daraja-mock: the missing test story

pesa-playground (53 stars, last active January 2026) attacks a different problem: the Daraja sandbox itself is slow, stateful, and requires internet, so integration tests against it are miserable. it is a local M-Pesa ecosystem simulator (Rust/Tauri with a SvelteKit UI) where you can trigger payments and callbacks deterministically. paired with daraja-mock on PyPI, this is the beginning of an actual testing story for M-Pesa integrations, which the ecosystem has never had.

OmentaElvis/pesa-playground repository on GitHub

mpesa-mcp: payments for AI agents

mpesa-mcp wraps Daraja (plus Africa's Talking) as an MCP server so AI agents can initiate STK pushes, check transaction status, and send airtime. it is number two in the download chart, partly inflated by MCP registry mirrors (mseep-daraja and iflow-mcp-daraja in the catalog are automated republications of MCP servers), but the signal is real: the newest integration surface for M-Pesa is an agent runtime rather than a web framework. whether you find that exciting or terrifying probably depends on how much you trust an LLM with your paybill.

gabrielmahia/mpesa-mcp repository on GitHub

so what does a sensible Django integration look like?

full disclosure: one row in this dataset is mine (achingachris/mpesa-daraja-3.0, a Daraja playground i have been building, and conveniently the most recently updated repo in the list). so this section is opinionated, but it is opinionated from having now read 160 attempts at the same problem.

achingachris/mpesa-daraja-3.0 Daraja playground repository

after all this reading, i think the right architecture for Daraja in Django is a thin service layer plus a serious callback story, whether you hand-roll it or install it:

1. a service module, not calls in views. one file (services.py) that owns every HTTP interaction with Daraja. views and tasks call functions like stk_push(...); nothing else in the codebase knows the API exists.

2. cache-backed token management using Safaricom's own expiry. the token endpoint returns expires_in; respect it instead of hardcoding 50 minutes, and scope the cache key by environment so a sandbox token can never leak into production calls:

def get_access_token() -> str:
    key = f"daraja:access_token:{settings.MPESA_ENVIRONMENT}"
    token = cache.get(key)
    if token:
        return token
    response = requests.get(
        f"{base_url()}/oauth/v1/generate?grant_type=client_credentials",
        auth=(settings.MPESA_CONSUMER_KEY, settings.MPESA_CONSUMER_SECRET),
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()
    token = data["access_token"]
    cache.set(key, token, timeout=int(data.get("expires_in", 3599)) - 60)
    return token

Django's cache framework gives you the atomicity story the database-row approach struggles with, and in production it is Redis anyway.

3. audit everything. two models: one row per outbound API call (request, response, status), one row per inbound callback. when a customer says "i paid but the order is stuck", this table is the difference between a two-minute lookup and a support nightmare.

4. treat callbacks as hostile until proven otherwise. yes, the endpoint needs csrf_exempt; no, that does not mean trusting the payload. record the raw callback, make processing idempotent on CheckoutRequestID (Safaricom retries), and either allowlist Safaricom's IPs or confirm every callback with a transaction status query before releasing goods.

5. process callbacks asynchronously. answer Safaricom with a 200 immediately, do your fulfillment in a Celery task. slow callback endpoints are how you end up debugging duplicate deliveries.

6. secrets in the environment, environments split hard. MPESA_ENVIRONMENT=sandbox|production switching the base URL, credentials from env vars, and nothing in git. after seeing 32 repos with keys in source, i will keep repeating this.

if you want this prebuilt: django-daraja gives you items 1, 2 (roughly) and 6; mainfinity-django-mpesa gives you nearly all of it; mpesakit gives you 1, 2 and 6 framework-agnostically and leaves 3, 4, 5 to your app, which is a reasonable split.

verdicts

my honest recommendations, project by project:

  • a sandbox STK push demo today: use django-daraja. fastest install-to-prompt path, docs are great; just know its limits.
  • production Django payments: use mainfinity-django-mpesa, or your own service layer. it is the only Django package with idempotent callbacks and reconciliation built in; audit it first, it is young.
  • FastAPI/Flask/scripts, full API surface: use mpesakit. typed, async, tested, active; the best pure engineering here.
  • local testing without the sandbox: use pesa-playground + daraja-mock. deterministic callbacks beat praying to ngrok.
  • AI agent payments: use mpesa-mcp. it is early and you should sandbox it, but this is where things are heading.
  • to learn how Daraja works: write the raw requests yourself once. the API is small; just never let the tutorial pattern near production.

and the meta-verdict on the whole ecosystem: it does not need a 48th package. it needs consolidation (maintainers merging efforts the way Byte-Barn formed around mpesakit), it needs Safaricom to either maintain an official SDK or bless a community one, and it needs the companies quietly running battle-tested Daraja code in production to open-source the parts that are not secret sauce. the raw material is all there; 191 projects proves there is no shortage of effort, only a shortage of coordination.

appendix: the full catalog

every unique project from the dataset, with stats as of August 4, 2026. "dl/mo" is PyPI downloads in the last 30 days. approach codes: LIB = reusable library/SDK, DIY = app making raw HTTP calls to Daraja, DD-FORK = fork/mirror of django-daraja, DD-USER = app installing django-daraja, PKG = PyPI-only (no public repo), DEMO = example/tutorial/other, DEAD = deleted from GitHub.

the complete per-project table (all 191 entries with stars, last commit, monthly downloads, approach and verdict) lives in full-catalog.csv in this article's page bundle. if you want a specific project's verdict, ask in the comments and i will pull it up.

sources and references

analysis pipeline: 163 repos shallow-cloned and statically analyzed with Python, download and release data from the PyPI JSON API and pypistats.org, screenshots captured August 4, 2026.

Written and Authored by Chris