OTP SMS in Kenya: How to Send Verification Codes with an SMS API

By Brian Published 5 min read
OTP SMS in Kenya: sending verification codes with an SMS API

One-time passwords (OTPs) are the most common SMS any Kenyan app sends: sign-up verification, login codes, password resets and payment confirmations. Get them right and users barely notice; get them wrong and people can't log in. This guide shows how to send OTP SMS in Kenya with an SMS API: a working Python example, how to generate and check codes safely, what it costs, and the mistakes that cause failed or abused OTPs.

Short answer: generate a random 6-digit code on your server, store only a hash of it with a 5-minute expiry, and send it as a transactional SMS from your registered Sender ID through the Brilio SMS API. Each OTP costs KES 0.40 to KES 0.80, and sandbox keys let you test without sending or paying.

How OTP verification works

  1. The user enters their phone number in your app.
  2. Your server generates a random code, saves a hash of it with an expiry time, and sends the code by SMS.
  3. The user types the code into your app.
  4. Your server checks it: correct, not expired, not already used. If it matches, the phone number is verified.

The SMS provider only delivers the message. Generating and checking the code is your server's job, which is what keeps it secure.

Send an OTP with the Brilio SMS API

Get an API key from your portal (Settings → API Keys). Use a sandbox key while building: it returns realistic responses without sending messages or charging your wallet.

import hashlib
import os
import secrets
import time

import requests

API_URL = "https://sms-api.briliotechnologies.com/v1/messages/send/"


def send_otp(phone, store):
    code = f"{secrets.randbelow(1_000_000):06d}"            # 6 random digits
    store[phone] = {
        "hash": hashlib.sha256(code.encode()).hexdigest(),   # never store the code itself
        "expires": time.time() + 300,                         # valid for 5 minutes
        "attempts": 0,
    }
    response = requests.post(
        API_URL,
        headers={"Authorization": f"Bearer {os.environ['BRILIO_API_KEY']}"},
        json={
            "send_path": "direct",
            "sms_type": "transactional",
            "sender_id": "YOURBRAND",
            "message": f"Your YOURBRAND code is {code}. It expires in 5 minutes. Never share it.",
            "recipients": [phone],
        },
        timeout=30,
    )
    response.raise_for_status()                               # 201 on success


def check_otp(phone, entered, store):
    entry = store.get(phone)
    if not entry or time.time() > entry["expires"] or entry["attempts"] >= 5:
        return False
    entry["attempts"] += 1
    if hashlib.sha256(entered.encode()).hexdigest() == entry["hash"]:
        del store[phone]                                      # one use only
        return True
    return False

In production, store would be your database or cache (for example Redis) rather than a Python dictionary. The same request works from Node.js, PHP or any language; see the bulk SMS API guide for examples in each.

OTP security checklist

  • Random codes only. Use a cryptographic generator (secrets in Python), never random or sequential numbers.
  • Store a hash, not the code, so a database leak doesn't expose live codes.
  • Short expiry: 5 to 10 minutes is enough.
  • One use only: delete or mark the code as used once it's verified.
  • Limit attempts: lock the code after about 5 wrong tries.
  • Limit resends: wait 30 to 60 seconds between resends and cap them per hour, so nobody can run up your SMS bill or spam a number.
  • Never log codes in application logs or error reports.
  • Say "never share this code" in the message, which helps protect users from phone scams.

Write a good OTP message

Your YOURBRAND code is 482913. It expires in 5 minutes. Never share it.

  • Put your brand first, so the user knows which app it's for.
  • Keep it under 160 characters and avoid emoji, so it's one SMS.
  • No links. Links in OTP messages look like phishing and teach users bad habits.
  • Send it from your own Sender ID. A recognisable name is more trusted than a number. See Sender ID registration in Kenya.

Always send OTPs as transactional

OTPs, receipts and alerts are transactional messages: the user asked for them. Marketing is promotional. Keep them separate: send OTPs with "sms_type": "transactional" and never put offers in an OTP message. Mixing the two breaks network rules and can get a Sender ID suspended.

What OTP SMS costs

Each OTP is one SMS: KES 0.40 to KES 0.80 at Brilio depending on the network and your top-up, with no monthly fee.

OTPs a month Approximate cost (Safaricom)
5,000 KES 3,500 (Starter rate, KES 0.70)
50,000 KES 30,000 (Growth rate, KES 0.60)
300,000 KES 150,000 (Business rate, KES 0.50)

Resend limits matter here: every unnecessary resend is a paid SMS. Full prices: bulk SMS prices in Kenya.

When an OTP doesn't arrive

  • Check the number format: 0712345678 or 254712345678.
  • Check your Sender ID is approved on that network.
  • Check the API response: 401 is a wrong or missing key, 429 means you're sending too fast (wait and retry), and 502 means the network couldn't process the request (safe to retry).
  • Check delivery in your portal, where each message has a delivery status.
  • Offer a fallback: after two failed SMS attempts, let users verify another way, such as email.

See the full integration walkthrough on video:

Video: How to Integrate SMS into Your Website in Kenya · Watch on YouTube

Related guides: Bulk SMS API in Kenya · Send bulk SMS from Excel · Bulk SMS in Kenya

Frequently asked questions

How do I send OTP SMS in Kenya?

Generate a random code on your server, store a hash of it with an expiry, and send the code as a transactional SMS from your Sender ID through an SMS API such as Brilio's. Check the code on your server when the user enters it.

How much does an OTP SMS cost in Kenya?

At Brilio, KES 0.40 to KES 0.80 per OTP depending on the network and your top-up, with no monthly fee. A message under 160 characters is billed as one SMS.

How long should an OTP be valid?

Five to ten minutes is standard. Shorter is safer, but give users enough time for the SMS to arrive and be typed in.

Should OTPs be transactional or promotional SMS?

Transactional. OTPs are messages the user requested, so send them with the transactional type and never include marketing content.

Can I test OTP sending without paying?

Yes. Brilio sandbox API keys return realistic responses without sending real messages or charging your wallet, so you can build and test your OTP flow first.

Building phone verification into your app? Start with bulk SMS in Kenya or create a free account to get your API key.

Your Partner In Communications & Automation.