Why goAML reporting is still a manual job in Kenya, and how we're automating it
Every bank, microfinance bank, SACCO, remittance provider and forex bureau in Kenya is a reporting institution under the Proceeds of Crime and Anti-Money Laundering Act. When something looks suspicious, the institution files a report with the Financial Reporting Centre (FRC), and since the FRC adopted goAML, that report is a structured XML document, not a letter.
I recently built a goAML report generator from scratch. This post is about what I learned along the way, why I think most institutions are still doing this by hand, and what an automated version looks like.
What actually goes into a goAML report
A Suspicious Transaction Report (STR) is a tree. At the top sits the reporting entity, the officer filing it, and a free-text narrative explaining the grounds for suspicion. Under that sit the transactions, and every transaction has two sides: where the money came from and where it went.
Each side has to be modelled precisely:
- Is the party your customer (a
my_clientnode, with full KYC detail) or someone else? - Is it an account, a person, or a legal entity?
- Which channel moved the money (cash, M-Pesa, PesaLink, RTGS, SWIFT) and does the party type make sense for that channel?
<transaction> <transactionnumber>PL26251C11</transactionnumber> <transmode_code>PSL</transmode_code> <amount_local>280000.00</amount_local> <t_from_my_client> … your customer's account, signatory, KYC … </t_from_my_client> <t_to> … the counterparty's account at another bank … </t_to> </transaction>
A Suspicious Activity Report (SAR) has a different shape: instead of transactions it describes the parties involved and why they matter. Same schema, different branch of the tree.
Two more report types complete the set. A Cash Transaction Report (CTR) covers cash transactions at or above the USD 15,000 threshold, suspicious or not, and is usually the highest-volume report an institution files. A Suspicious Transaction and Activity Report (STAR) combines both shapes for cases that need the transactions and the wider activity behind them. The generator I built handles all four: CTR, STR, SAR and STAR.
Why uploads get rejected
goAML is strict, and that's a good thing. But it means small data problems become failed submissions. The common ones are boringly predictable:
- Placeholder names. A cash depositor recorded as "Unknown" or "N/A".
- Channel/party mismatches. An RTGS transfer whose counterparty is a person rather than an account.
- Missing my-client detail. Your own customer without an ID number or account-opening date.
- Format drift. Phone numbers as
0722…in one system and+254722…in another; dates in three formats. - Thin narratives. "Suspicious deposits" is not grounds for suspicion. The FRC needs who, what, when and why.
None of these are hard to fix. They're just hard to catch when the report is being assembled by hand from screenshots of a core banking screen.
Validate first, generate second
The design decision that made the biggest difference was simple: never generate XML that you haven't validated. The same rule engine runs when a compliance officer clicks "Validate" and again immediately before the XML is built. Every rule is a pure function over the report data, so it's testable in isolation and there's exactly one definition of "valid".
Rules come in two strengths:
- Blocking: things goAML will reject (missing mandatory fields, impossible channel/party combinations, duplicate references). No XML until they're fixed.
- Warnings: things that are legal but probably wrong (a Kenyan ID with four digits, a narrative under 200 characters, several cash deposits sitting just under KES 100,000 without the structuring indicator ticked).
Every issue points at the exact row and field, so the fix takes seconds. In practice that turned a report that took a morning to assemble into one that takes a couple of minutes to review.
Try it yourself. We've published a browser-only proof of concept (see also the goAML Reporter product page). Load the sample case. It includes one deliberately broken transaction, so you can see the validator catch it. Then fix it and download the XML. Nothing you type leaves your browser.
Why this matters beyond one institution
Kenya has roughly 38 commercial banks, 14 microfinance banks, over 170 deposit-taking SACCOs, and well over a hundred remittance providers, forex bureaus and digital lenders, all filing to the same FRC, against the same schema. Most are solving the same problem independently, often with spreadsheets.
That's why I've refocused my company, formerly WaveOdooApps Consulting, now Humus Technologies, on regulatory reporting. goAML Reporter is our first product: it covers all four report types, connects to your core banking data, validates against goAML rules, generates the XML, and keeps an audit trail of everything filed.
If you're an MLRO, head of compliance or CTO at a Kenyan financial institution and this sounds familiar, I'd like to hear how you're handling it today.