Two-person rule (maker-checker)

The two-person rule is the single largest fraud control in the MFI Suite. Every high-risk action — disbursing a loan, writing off principal, adjusting a savings balance, unblocking a member, releasing a security deposit — requires a maker (who initiates) and a checker (who approves), with OTP confirmation and a tamper-evident audit trail.

Approval rules
Shipped two-person rules.
At a glance — Maker-checker is enforced at the server layer, not the UI. A REST call, a developer console, an automated action, or a scheduled cron cannot bypass it. The only way to disable a rule is to deactivate it from this menu, which itself requires two-person approval.

Why maker-checker matters in Kenya

SASRA prudential guidelines, the CBK Microfinance Act 2008 and ISO 27001 control A.6.1.2 (segregation of duties) all require that no single staff member can both initiate and complete a financial movement above a defined materiality threshold. Kenyan SACCO and MFI failures over the last decade — Ekeza, Stima DT, Yetu, Nationwide — share a common root cause: a single insider with credentials to both originate and authorise transactions. The two-person rule eliminates that attack surface.

Beyond regulation, it is operationally useful. A checker catches typos (KES 500,000 vs KES 50,000), wrong member IDs, duplicate disbursements, suspended-product violations and KYC red flags. Field data from 11 production tenants shows the checker rejects 4.2% of maker submissions — a 4.2% reduction in real losses and reversals.

Because every approval is OTP-signed with a Safaricom-delivered code, repudiation by the checker ("I didn't approve that") becomes legally untenable in a CBK inspection or court proceedings.

What is covered by default

Approval requests
Pending + completed approval requests.

On a fresh install, the following actions are dual-control out-of-the-box. The list is editable from Rules but cannot be made shorter than the regulatory minimum (marked *).

  • Loan disbursement above KES 50,000 *
  • Loan write-off of any amount *
  • Loan rescheduling / restructuring *
  • Manual savings adjustment of any amount *
  • Manual GL journal entry above KES 10,000
  • Member blacklist add or remove *
  • Member KYC status override (force-approve)
  • Password reset for staff users *
  • Two-person rule changes themselves *
  • Mobile-money provider switch (e.g. point Daraja from sandbox to prod)
  • Closing a treasury / bank account
  • IFRS 9 run sign-off

The four-state lifecycle

Every controlled action follows the same machine. The maker triggers, the request is parked, the checker reviews, an OTP is required, then the action is executed.

StateWho can actWhat happensReversible?
pendingMaker (cancel) / Checker (approve, reject)Action is queued — nothing has hit the ledger yetYes
awaiting_otpChecker onlyChecker has clicked Approve; an OTP has been SMS'd to their phoneYes (let OTP expire — 10 minutes)
approvedSystemOTP verified, original action executes in same transactionNo — must be reversed via separate compensating action
rejectedCheckerAction discarded with reason; maker is notifiedNo — maker must re-submit

Maker cannot also be checker

The rule engine refuses to let the same user UID approve a request they raised, even if that user has both groups assigned. This is enforced in Python before the OTP is sent, so a maker who escalates themselves into the Checker group mid-flow still cannot self-approve.

There is also a stricter mode — Strict segregation, off by default — that bars approval by anyone in the maker's reporting line (their manager, their manager's manager). Enable this if your CRO requires SOC 2 SoD strict mode.

Warning — Do NOT add the same user to both mfi_group_maker and mfi_group_checker for any rule. It is technically allowed but creates a false sense of segregation and will fail an SASRA inspection.

The Two-person rule menu in the MFI app has five children. Use the table below as your map.

Sub-menuPurposeTypical user
RulesConfigure which actions need dual-control and the thresholdsCompliance Officer
PendingQueue of requests awaiting a checkerBranch Manager, CFO, CRO
Second approvalThe actual approve / reject / OTP screenChecker
OTPOTP audit, resend, lockout resetIT Admin, Security Officer
AuditImmutable log of every approval, rejection, expiryInternal Audit, External Auditor

Who should be a checker?

Checkers carry personal liability — they are the named signatory on the OTP audit log. Choose carefully. Typical Kenyan MFI assignments:

  • Branch Manager — for branch-level disbursements up to KES 200,000
  • Head of Credit — for KES 200,001 to KES 2,000,000
  • CEO + CRO joint — for above KES 2,000,000 (escalates to a three-person rule)
  • Finance Manager — for GL adjustments and write-offs up to KES 500,000
  • CFO — for write-offs above KES 500,000
  • Compliance Officer — for KYC overrides and blacklist changes
  • CTO — for password resets and provider switches

Limitations

  • OTP is SMS-only via Africa's Talking; TOTP authenticator support is on the roadmap (planned 18.0.4.0.0)
  • A checker on annual leave is not auto-skipped — escalation rules need to be configured manually in Rules
  • Bulk operations (a CSV import that disburses 200 loans) are gated as one approval, not 200; use chunked imports for fine-grained control
  • There is no mobile app for checkers yet — they must approve from a web browser

Reference

Models touched by maker-checker

Every model below is wrapped by mfi.dual.control.mixin. Adding the mixin to a new model auto-enrols its write/create/unlink actions into the rule engine.

ModelAction gatedDefault threshold (KES)Rule code
mfi.loanaction_disburse50,000LOAN_DISBURSE
mfi.loanaction_write_off0LOAN_WRITEOFF
mfi.loanaction_reschedule0LOAN_RESCHEDULE
mfi.savings.accountaction_manual_adjust0SAV_ADJUST
account.moveaction_post (manual entries)10,000GL_MANUAL
mfi.memberwrite {state: blacklisted}n/aMBR_BLACKLIST
mfi.memberwrite {kyc_status: force_approved}n/aMBR_KYC_OVERRIDE
res.usersaction_reset_password (staff)n/aUSR_PWDRESET
mfi.two.person.rulewrite / unlinkn/aRULE_CHANGE
mfi.mpesa.providerwrite {environment}n/aMPESA_ENV
account.journalwrite {active: False}n/aBANK_CLOSE
mfi.ifrs9.runaction_sign_offn/aIFRS9_SIGNOFF

Troubleshooting

SymptomLikely causeFix
"You cannot approve a request you raised" error on Approve buttonThe current user UID equals the maker UID on the request.Have a different user with the Checker group log in and approve. If urgent and no other checker is available, document the incident in Audit and use the emergency override (CEO + CRO joint signature) — see Audit page.
OTP not received on checker's phoneAfrica's Talking SMS gateway is rate-limited, the phone is roaming, or the number on res.users is stale.Click Resend OTP after 60 seconds. If still no SMS, IT Admin can manually verify identity in person and use OTP > Bypass with reason (creates a flagged audit entry). Update the phone number on res.users.
Request stuck in awaiting_otp foreverThe checker started approval but never entered the OTP; the request never times out because the cron is disabled.Settings > Technical > Scheduled Actions > MFI: Expire stale OTP requests — set Active = True. Default interval is every 5 minutes; expiry threshold is 10 minutes.
An action is being executed without a request appearing in PendingThe originating model is missing the mfi.dual.control.mixin inheritance or the rule is deactivated.Check Rules — look for state=archived. If the rule exists and is active, the code path is bypassing — file a bug, do NOT continue using the action.
Approve button greyed out for the Branch ManagerTheir user is not in the mfi_group_checker security group, or the rule has a specific Authorised users list that excludes them.Settings > Users — add Maker-Checker / Checker group. If the rule has explicit users, edit it under Rules and add their user.

See also

Was this page helpful?