OTP delivery and management

Every approval that moves money or changes risk posture is signed with a 6-digit OTP delivered to the checker's registered phone via Africa's Talking. The OTP is the legal signature of the checker — without it, no money moves.

Approval request
A pending request — concurrer sees record, clicks Concur or Reject.
At a glance — OTPs are 6 digits, 10-minute lifetime, 3-attempt limit before 15-minute lockout. Generation is cryptographically random (secrets.randbelow on the server, not predictable). The hash — not the plaintext — is stored in the database.

How OTPs are generated

When a checker clicks Approve, the server calls Python's secrets.randbelow(1_000_000) to produce a 6-digit code. The plaintext is sent to Africa's Talking and immediately discarded from server memory. The SHA-256 hash plus a salt is stored on the mfi.dual.control.otp record.

When the checker enters their code, the server hashes the input and compares it constant-time to the stored hash. There is no "decrypt the OTP" — the plaintext truly does not exist on the server side after dispatch.

SMS delivery via Africa's Talking

Africa's Talking is the configured SMS gateway for Kenya (best deliverability, ~2 second median). The sender ID is the tenant's registered Sender ID (e.g. MWANANCHIMFI). The message template is:

Other channels supported (configurable per rule on the OTP tab): e-mail (slower, less safe), Twilio (fallback if Africa's Talking is down), Both (SMS + e-mail simultaneously). Voice call OTP is on the roadmap.

Your MWANANCHIMFI approval code is 738241. Do not share. Valid 10 min. Ref DCR-2026-02144.

Validation rules

  • Exactly 6 numeric digits — anything else rejected client-side
  • Lifetime 10 minutes from dispatch; configurable per rule but min 5 max 30
  • 3 wrong attempts triggers 15-minute lockout on that OTP slot
  • Resend after 60 seconds; new OTP invalidates the old one
  • Re-using a successful OTP is rejected ("OTP already consumed")
  • OTPs are scoped to a single request — you cannot use OTP from DCR-001 to approve DCR-002

OTP audit log

Under OTP menu you see every OTP dispatch in the last 90 days. Columns: dispatch time, user, channel, request ref, result (Verified / Expired / Wrong / Locked / Bypassed). This is read-only for everyone including admins — it cannot be edited or deleted.

External auditors typically sample 30-50 rows from this table for a compliance review. Provide them read access via the Auditor security group; they cannot mutate.

Emergency bypass procedure

Africa's Talking outages happen — about 2-3 times per year for short periods (15-90 min). Disbursements cannot wait. Bypass is the emergency escape valve.

The IT Admin or CTO opens OTP > Bypass. The screen asks for:

Bypass requires TWO authorisers (CTO + CEO, or CFO + CRO). Both sign in person at the same terminal. The bypass is logged with a BIG RED flag in Audit and triggers an automatic e-mail to the Internal Auditor.

  • Request ID (e.g. DCR-2026-02144)
  • Checker's user ID
  • Bypass reason — free text, mandatory 50+ characters
  • Bypass evidence attachment — typically a screenshot of the AT status page or a phone verification e-mail
Warning — Bypass should be used ≤ 6 times per year. SASRA inspectors look at the bypass count as a leading indicator of weak SMS dependency. If you're bypassing more than monthly, switch the primary OTP channel to e-mail or add Twilio as fallback.

Managing checker phone numbers

Phone numbers live on res.users.phone (or x_otp_phone if set — overrides res.users.phone). Updates require dual-control under the USR_PHONE_CHANGE rule (add this if not default). Format: +254XXXXXXXXX (E.164). Africa's Talking rejects local-format (07XXXXXXXX).

When a checker changes phones (new SIM, lost phone), they file a ticket with IT. IT updates the number, which triggers USR_PHONE_CHANGE dual-control. A test OTP is sent before the change is committed.

Note — Personal phones only — never shared phones. The legal signature relies on "only the checker received the SMS".

Worked scenarios

Scenario — Africa's Talking outage during salary day

Setting: Friday 28th, peak disbursement. Africa's Talking status page goes red at 13:42 — SMS delivery suspended Kenya-wide.

CharacterRole
Kimani MwangiCTO
Samuel KibetCEO
Aisha HassanBranch Manager (checker, locked out)
Jane WambuiLoan Officer (waiting)

Timeline

  1. 13:43: Aisha tries to approve DCR-03101 — clicks Approve, no SMS (OTP dispatched OK from our side, but AT queue is stuck)
  2. 13:45: Aisha clicks Resend twice — still nothing (—)
  3. 13:47: Aisha phones IT — Kimani confirms AT outage on status page (—)
  4. 13:50: Kimani opens OTP > Bypass (Form requires CEO co-signature)
  5. 13:51: Samuel walks to Kimani's desk (—)
  6. 13:52: Kimani enters DCR-03101, Aisha's UID, reason: "AT outage 13:42 onwards, status page screenshot attached" (Attachment saved)
  7. 13:53: Samuel co-signs with his BridgeERP credentials (Bypass committed — RED FLAG in Audit)
  8. 13:53: Underlying disbursement fires (M-Pesa B2C sends KES 95,000 to borrower)
  9. 13:54 - 14:30: Same procedure for 11 more requests (All bypassed by CTO+CEO)
  10. 14:31: AT comes back; bypass mode disabled (Normal SMS flow resumes)
  11. Monday: Internal Audit reviews 12 bypassed requests — all verified legitimate (Audit memo filed)

Outcome — 12 legitimate disbursements processed during outage. Zero fraud. SASRA inspector at next visit confirms the bypass procedure is acceptable for documented outages.

Reference

OTP channel comparison

ChannelMedian deliveryCost / OTP (KES)ReliabilityRecommended use
Africa's Talking SMS2 sec1.2099.5%Primary — default for Kenya
Twilio SMS4 sec3.5099.7%Fallback only
E-mail30 sec097% (spam folder risk)Low-risk rules or fallback
Voice call (roadmap)10 sec8.0099.9%Future

OTP configuration per rule

FieldDefaultMinMax
Length (digits)668
Lifetime (minutes)10530
Max attempts325
Lockout (minutes)15560
Resend cooldown (seconds)6030120
ChannelSMS

Troubleshooting

SymptomLikely causeFix
OTP arrives but says "Invalid"Typo or expired (>10 min old).Click Resend OTP. New code arrives; enter promptly.
No OTP ever arrivesAT outage, stale phone number, or carrier filtering (some networks block short-code sender IDs).Resend after 60s. If repeat, IT verifies res.users.phone. If outage confirmed via AT status page, use Bypass.
Locked out for 15 minutes after 3 wrong attemptsUser mistyped or copied wrong code.Wait it out, or IT can clear the lockout via OTP > Reset lockout (logged).
OTP cost in Africa's Talking dashboard much higher than expectedBypass cron is dispatching test OTPs to all checkers nightly (set up by a previous admin).Settings > Scheduled Actions > MFI: OTP keepalive — disable. Test OTPs are not required.
Auditor wants OTP plaintext for forensic reviewPlaintext is not stored — only the SHA-256 hash.Explain the hash design. AT keeps a delivery log for 30 days — pull from there if needed for that window.

See also

Was this page helpful?