Why OTPs matter more in fintech
If you're building a fintech app, mobile money service, banking platform, or any tool that handles money in Uganda, OTPs (one-time passwords) aren't a nice-to-have — they're fundamental to security and customer trust.
A 5-second slow OTP loses customers. A 60-second delay loses accounts. A delivery failure can mean lost transactions and angry users.
This guide covers everything you need to know about implementing OTP SMS in Uganda: speed benchmarks, compliance requirements, code samples, and the gotchas that catch most developers.
OTP delivery speed — the benchmark to hit
Industry standard for OTP delivery:
- Excellent: Under 5 seconds
- Acceptable: 5-15 seconds
- Poor: 15-30 seconds (users start retrying)
- Unacceptable: 30+ seconds (you're losing transactions)
Yoola SMS's transactional route consistently delivers OTPs in under 15 seconds across all major Uganda networks. For the most demanding fintech use cases, we offer a premium route under 8 seconds.
The 3 types of OTP every fintech needs
1. Login OTP
Sent when a user logs in from a new device or after timeout. Example:
Your MYAPP login code is 482917. Do not share with anyone. — MYAPPOTP
Validity: 5-10 minutes is standard.
2. Transaction OTP
Sent before a user confirms a money transfer, withdrawal, or sensitive action. Example:
Confirm transfer of UGX 500,000 to John (256772123456). Code: 195832. Do not share. — MYBANKOTP
Validity: 2-5 minutes (shorter for security).
3. Account verification OTP
Sent during signup to verify the user owns the phone number. Example:
Welcome to MYAPP! Your verification code is 716290. Enter this to complete signup. — MYAPP
Validity: 10-30 minutes (longer for first-time users).
OTP best practices for Uganda
1. Use a dedicated transactional sender ID
Don't mix OTP traffic with promotional. Best practice:
- Promotional SMS → "MYAPP" sender ID
- OTP SMS → "MYAPPOTP" sender ID
This makes it crystal clear to users which messages are time-sensitive. Yoola SMS supports multiple sender IDs per account.
2. Make OTP codes 6 digits
6-digit codes are the sweet spot — secure enough to prevent brute force, short enough that users type them quickly. 4 digits is too few (10,000 possibilities); 8 digits is too many to type.
3. Always include "Do not share" warning
Social engineering attacks where attackers call users asking for their OTP are common in Uganda. Always include a warning:
Your OTP is 123456. Yoola SMS will NEVER call you for this code. — MYAPPOTP
4. Set sensible expiry times
- Transaction OTPs: 2-5 minutes
- Login OTPs: 5-10 minutes
- Account creation: 10-30 minutes
Too short and users miss the SMS. Too long and stolen OTPs remain useful.
5. Rate limit OTP requests
Don't let a user request 100 OTPs in 5 minutes — that's both wasteful and a security risk. Best practice:
- Max 3 OTPs per phone per 10 minutes
- Max 10 OTPs per phone per 24 hours
- After exceeding, require a different verification (email, call)
6. Implement fallback to voice OTP
If an SMS OTP doesn't arrive in 60 seconds, offer the user a "Call me with the code" option. Some users in Uganda still have features phones where SMS occasionally delays.
Complete OTP implementation — PHP example
<?php
// === OTP send endpoint ===
function sendOTP($phone) {
global $pdo;
// Rate limit check
$check = $pdo->prepare(
"SELECT COUNT(*) FROM otps WHERE phone = ? AND created_at > NOW() - INTERVAL 10 MINUTE"
);
$check->execute([$phone]);
if ($check->fetchColumn() >= 3) {
return ["error" => "Too many OTP requests. Wait 10 minutes."];
}
// Generate OTP
$otp = sprintf("%06d", random_int(0, 999999));
// Save hashed OTP with 5-minute expiry
$stmt = $pdo->prepare(
"INSERT INTO otps (phone, otp_hash, expires_at, created_at)
VALUES (?, ?, NOW() + INTERVAL 5 MINUTE, NOW())"
);
$stmt->execute([$phone, password_hash($otp, PASSWORD_DEFAULT)]);
// Send via Yoola SMS
$message = "Your verification code is {$otp}. Valid for 5 minutes. Do not share. — MYAPPOTP";
$ch = curl_init("https://yoolasms.com/api/v1/send.php");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
"api_key" => YOOLA_API_KEY,
"phone" => $phone,
"message" => $message,
"sender" => "MYAPPOTP"
]),
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_RETURNTRANSFER => true
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
return $result['status'] === 'success'
? ["sent" => true]
: ["error" => "SMS delivery failed. Try again."];
}
// === OTP verify endpoint ===
function verifyOTP($phone, $userInput) {
global $pdo;
$stmt = $pdo->prepare(
"SELECT id, otp_hash FROM otps
WHERE phone = ? AND expires_at > NOW() AND used_at IS NULL
ORDER BY id DESC LIMIT 1"
);
$stmt->execute([$phone]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row || !password_verify($userInput, $row['otp_hash'])) {
return ["valid" => false, "error" => "Invalid or expired code"];
}
// Mark as used
$pdo->prepare("UPDATE otps SET used_at = NOW() WHERE id = ?")->execute([$row['id']]);
return ["valid" => true];
}
?>
Compliance — what you need to know
Bank of Uganda regulations
If you're a regulated financial institution (bank, microfinance, mobile money), the Bank of Uganda has specific guidelines:
- OTPs must be cryptographically random (not predictable)
- OTPs must expire within reasonable time (5-15 minutes typical)
- OTPs must not be reusable
- OTP audit logs must be retained for 7+ years
- Customer data (phone numbers used for OTP) must be kept secure
Yoola SMS's OTP logs are retained and accessible via API for compliance reporting.
UCC (Uganda Communications Commission) rules
- Transactional SMS (including OTPs) can be sent 24/7 (promotional is restricted to 9am-9pm)
- Sender IDs must be registered (we handle this)
- Subscribers can request "do not contact" — but OTPs they request themselves are always allowed
NITA-U data protection
Uganda's Data Protection and Privacy Act (2019) requires:
- Lawful basis for processing phone numbers
- Customer consent (implicit for OTPs when they request the verification)
- Data minimization (only collect numbers you need)
- Secure storage
How much does OTP SMS cost?
OTP SMS in Uganda costs the same as regular SMS through Yoola SMS — there's no premium for transactional routing on most accounts. At typical volumes:
- 1,000 OTPs/month = ~UGX 30,000
- 10,000 OTPs/month = ~UGX 250,000
- 100,000 OTPs/month = ~UGX 2,000,000
For very high-volume fintechs (100,000+ OTPs/month), we offer custom enterprise pricing — contact sales.
Ready to add OTP to your app?
Get your free Yoola SMS API key in 60 seconds. Test with 3 free SMS, then top up via Mobile Money when ready.
Comments (0)