African Tech Ecosystem

Why Africa Needs Its Own ERP Building Blocks (And Why We Open-Sourced Them)

We built APG because we kept copy-pasting the same M-Pesa wrapper into every project. The first time, we wrote it from scratch. The second time, we copy-pasted it into the new project and updated the credentials. The third time, we copy-pasted again, and then spent two days tracking down a bug that turned out to already exist in the version we'd copied from. The fourth time, we stopped and wrote a package.

That package is now apg-fintech-mpesa. It has been installed 8,400 times. That number tells us something: we were not the only team with that copy-paste problem.

This post is about why that problem exists, how badly it can go wrong, and why we decided that the right response was to open-source the solution instead of keeping it proprietary.

The Odoo Localization Problem

Before we talk about APG, it is worth being precise about the problem it is responding to, because "African software doesn't fit African business" is a claim that requires specifics to be useful.

Take Odoo. It is a genuinely good product, modular, open-source, with a large global community and a track record in SME deployments. We use it ourselves. But the Kenya localization module, as shipped, has several problems that are not edge cases. They are the core of what payroll software is supposed to do.

Odoo's Kenya PAYE module does not account for the Affordable Housing Levy that was introduced in the Finance Act 2023. Every employer in Kenya is required to deduct 1.5% of gross salary and match it with a 1.5% employer contribution. It is not optional, it is not sector-specific, and it is not new: it has been in effect for over two years. The base Odoo module does not calculate it.

The VAT configuration is wrong. Kenya currently operates a 16% standard VAT rate, an 8% rate for certain financial services and digital services supplied to non-residents, and zero-rating for a range of essential goods. These rates have been adjusted multiple times through successive Finance Acts. The Odoo base module carries configurations from an earlier period that do not reflect the current rate schedule. If you run VAT returns out of unmodified Odoo Kenya, you will file incorrect returns.

The NHIF integration, now the Social Health Authority under the SHIF reforms that took effect in 2024, uses contribution tables from the predecessor scheme. The new SHIF contribution structure replaced the old NHIF banding system with a 2.75% of gross salary model, with a separate Affordable Housing Levy and Social Health Insurance Fund deduction. Odoo's module has none of this.

We want to be clear: none of this is a criticism of Odoo as a company or a product. They build software for a global market. Maintaining current payroll tables for 54 African countries, each with different regulatory cycles, finance act timelines, and statutory body reorganizations, is not a reasonable expectation of a European software company. It is, however, a real problem for every Kenyan business trying to run payroll in Odoo without first hiring a developer to fix the localization.

That gap is exactly what APG addresses. Not by replacing Odoo, but by providing the African-specific modules that Odoo reasonably cannot maintain itself.

The M-Pesa Package Version History

The version history of apg-fintech-mpesa is essentially a log of payment integration incidents. We are going to describe it honestly, because the pattern is instructive.

Every version was a different incident report: v1 timed out silently. v2 retried transactions without idempotency keys and double-charged customers. v3 added idempotency keys but left the callback endpoint open: no signature verification, so anyone who knew the URL could trigger false payment confirmations. v4 is the version we would have written first if we'd known what we know now: idempotency on every outgoing request, HMAC signature verification on every callback, exponential backoff on retries, dead-letter queue for unprocessable callbacks. It took four versions and three production incidents to get there. The package exists so that nobody else has to pay for those same lessons.

The failure modes are specific and the fixes are non-obvious.

v1 (2018): Handled the happy path. The STK push went out, the callback came back, the payment was recorded. What it did not handle: Safaricom's API has a documented latency issue during peak hours where callbacks are delayed by 30–90 seconds, and during outages can be delayed indefinitely. Our v1 implementation had a 30-second timeout on the callback wait. If the callback arrived at 31 seconds, we returned a failure to the user and did not reconcile the eventual callback. We found out from a client whose payments had been silently failing for a week. The reconciliation gap was 47 transactions.

v2 (2020): Added retry logic. No idempotency keys on the outgoing STK push request. The M-Pesa API will process a retry as a new transaction if it cannot find the original request in its deduplication window, which is shorter than our retry interval was. Some customers were charged twice. The fix is an idempotency key tied to the internal transaction ID, sent with every STK push request. If the key matches a previous request still within the deduplication window, Safaricom returns the status of the original transaction instead of initiating a new one. We did not know this until we needed to know it.

v3 (2022): Idempotency keys on outgoing requests. Webhook signature verification was absent on the callback endpoint. Safaricom signs STK callback payloads with a shared secret. We were not verifying the signature. Anyone who knew our callback URL, which was not secret (it was registered in the Daraja portal), could POST a crafted payload that our system would treat as a successful payment. We discovered this via a penetration test, not via exploitation. We got lucky. The fix is HMAC verification on every inbound callback before any processing occurs.

v4 (2024): Idempotency keys + HMAC webhook verification + exponential backoff with jitter on retries + dead-letter queue for callbacks that arrive in an unparseable state (Safaricom has sent malformed JSON on at least two documented occasions during platform incidents). This is the version in the package. Every item on that feature list corresponds to a production incident either we experienced or a contributor reported.

The Governance Model

When we decided to open-source the APG packages, we spent time thinking about governance and landed somewhere simple: MIT license, no CLA (Contributor License Agreement), no corporate contributor agreement of any kind.

CLAs exist to protect companies from contributors later claiming intellectual property rights over their contributions. That is a legitimate concern for some projects. For APG, we made a different calculation. The African developer community contributing regulatory knowledge, current PAYE tables, updated NHIF/SHIF rates, correct SDL calculations for Tanzania, accurate PSSF deductions for Uganda, is doing so because they need the package to be correct for their own payroll runs. They are not potential IP claimants. They are the people whose payroll is wrong if the package is wrong. Adding a CLA to that contribution dynamic would reduce contributions for no meaningful benefit.

MIT because it is the license that corporate legal teams understand without needing to escalate. We have had companies tell us "our legal team won't let us contribute to GPL projects." We have never had a company tell us "our legal team won't let us contribute to MIT projects." MIT has never been the problem.

The 11pm Sunday PR

There is a moment that clarified for us why open-sourcing APG was the right call.

The community moment that made it worth it: A developer in Dar es Salaam submitted a pull request at 11pm on a Sunday. He had found a bug in the Tanzania Skills Development Levy calculation: the rate in the package was correct for employment income under a certain threshold but wrong for income above it, where the SDL rate steps up. His payroll run was scheduled for Monday morning. He did not email us to report the bug and wait. He cloned the repo, fixed the calculation, wrote a test that demonstrated the failure and the fix, and submitted the PR. We merged it at 6am Monday, released 6:30am. His payroll ran correctly. That is what open source is supposed to feel like.

The alternative, a proprietary package where that developer's only option was to file a support ticket and wait, means his Monday payroll would have run with incorrect SDL deductions, he would have had to manually correct them, and the fix would have benefited nobody else until we got around to releasing it. The open-source model produced a faster fix, a better test, and a corrected package for every other Tanzania payroll user who had the same bug and had not noticed yet.

Since that PR, we have had contributors fix NSSF calculation errors for Uganda, update the Ghana SSNIT contribution rates following the 2024 amendments, and add initial support for Ethiopia's Pension and Social Security Authority deduction schedule. None of these came from us. All of them came from developers whose payroll was wrong.

What We Are Building Toward

The current APG package set covers M-Pesa (Kenya, Tanzania), Airtel Money (Kenya, Uganda, Tanzania), MTN MoMo (Uganda, Ghana, Rwanda), and payroll statutory deductions for Kenya, Uganda, Tanzania, and Ghana. That is a starting point, not a destination.

The roadmap items we are actively working on:

PAPSS integration. The Pan-African Payment and Settlement System's transaction packets are documented. We are building the apg-papss package. No African developer should have to read a 200-page spec PDF and implement their own PAPSS client. They should install a package.

Digital shilling readiness. The Central Bank of Kenya has not committed to a CBDC launch timeline, but the design consultations are ongoing. When the CBK moves forward with a digital shilling, there will be an apg-digital-shilling package ready on day one. We are building the integration layer now against the published design specifications so that the community response to a launch is measured in hours, not weeks.

CBHIS billing for healthcare providers. The Community-Based Health Insurance Scheme billing API is the integration that every Kenyan clinic, pharmacy, and hospital running SHIF claims needs to implement. It is currently a custom integration job for every provider. It should be a package. We are building it.

The goal across all of this is the same: no Kenyan developer should ever have to reverse-engineer a regulatory API from a PDF document again. The first person who does that work, documents it in code, and publishes it under MIT has eliminated that cost for everyone who comes after them. We want to be that first person for the integrations that matter most, and we want the community to be that first person for the ones we have not gotten to yet.

Why Open Source

The argument for keeping APG proprietary was real. We could have built a subscription API layer around these packages, charged per-API-call for payroll calculations, and built a business around the compliance knowledge. Some companies have done exactly that in this space.

We chose not to for a reason that is partly pragmatic and partly principled. The pragmatic reason: proprietary compliance packages rot. Regulatory tables change. If our incentive is to charge for updates, we are fine. If our incentive is to be a credible infrastructure company for African software, being the company whose payroll package has the wrong NHIF rates six months after a reform is not a good look. Open source creates pressure to keep the package correct that proprietary models do not.

The principled reason: African developers have been paying a tax for twenty years in the form of having to implement the same integrations from scratch, over and over, because no shared infrastructure existed. APG is an attempt to stop paying that tax. You do not stop paying a collective tax by privatizing the solution to it.

The package is on GitHub. The license is MIT. Pull requests are open. If your payroll is wrong because our package is wrong, send the fix; we will ship it.