McTaba Labs logo
By Bonaventure Ogeto|

USSD App Development in Kenya: Building Your First Service

You build a USSD app by creating a web endpoint that receives POST requests from a USSD gateway and returns text menus. In Kenya, Africa's Talking is the most accessible gateway for developers. You register on their platform, get a USSD code in their sandbox, point it at your server, and your endpoint returns plain text that becomes the menu the user sees on their phone.

Why USSD still matters in Kenya

USSD works on every phone with a SIM card. No smartphone required. No data connection needed. No app to download. When a farmer in Nyandarua dials *384# to check their M-Pesa balance, that is USSD running on a 15-year-old Nokia.

In Kenya, USSD powers:

  • M-Pesa (the original killer USSD app)
  • Airtime and bundle purchases
  • Mobile banking for every major bank
  • Agricultural information services
  • Government services like eCitizen payments

If your product needs to reach people who do not have smartphones or reliable internet, USSD is not a fallback. It is the primary channel. About 30% of Kenyan mobile subscribers still use feature phones, and even smartphone users sometimes prefer USSD for quick transactions because it is faster than opening an app.

Setting up Africa's Talking sandbox

Africa's Talking is a Nairobi-based API company that provides USSD, SMS, voice, and airtime APIs across Africa. Their sandbox gives you a free USSD shortcode to test with.

  1. Create an account at africastalking.com
  2. Go to the sandbox app (the toggle at the top of the dashboard)
  3. Navigate to USSD > Create Channel
  4. Enter a USSD code (e.g., *384*123#) and point the callback URL to your server
  5. Note your API Key and username from the Settings page

The sandbox includes a phone simulator that lets you dial your USSD code from the browser. No real phone needed for testing.

Store your credentials:

# .env
AT_USERNAME=sandbox
AT_API_KEY=your_sandbox_api_key
AT_USSD_CODE=*384*123#

Your callback URL needs to be publicly accessible, just like M-Pesa callbacks. Use ngrok or a similar tunnel during development.

Building a USSD menu: the request and response model

When a user dials your USSD code, Africa's Talking sends a POST request to your callback URL with these fields:

  • sessionId: unique identifier for this USSD session
  • phoneNumber: the caller's number in international format
  • networkCode: the mobile network (e.g., 63902 for Safaricom)
  • serviceCode: the USSD code they dialed
  • text: the user's accumulated input, with each menu selection separated by *

Your server responds with plain text. The first character determines whether the session continues or ends:

  • CON (with a space): the session continues, show a menu and wait for input
  • END (with a space): the session ends after displaying the message

Here is a complete multi-level menu.

Node.js (Express):

import express from 'express';

const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

app.post('/ussd', (req, res) => {
  const { sessionId, phoneNumber, text } = req.body;

  // Split user input into levels
  const levels = text === '' ? [] : text.split('*');
  let response = '';

  if (levels.length === 0) {
    // First screen: main menu
    response = 'CON Welcome to McTaba Labs\n';
    response += '1. Check courses\n';
    response += '2. Talk to a mentor\n';
    response += '3. About us';
  } else if (levels[0] === '1') {
    if (levels.length === 1) {
      // Sub-menu: courses
      response = 'CON Available courses:\n';
      response += '1. Full-Stack AI Engineering\n';
      response += '2. M-Pesa Integration\n';
      response += '3. Tech Foundations';
    } else if (levels[1] === '1') {
      response = 'END Full-Stack AI Engineering\n';
      response += '26-week marathon.\n';
      response += 'Visit mctaba.com for details.';
    } else if (levels[1] === '2') {
      response = 'END M-Pesa Integration\n';
      response += 'Learn Daraja API, STK Push, webhooks.\n';
      response += 'Visit academy.mctaba.com';
    } else if (levels[1] === '3') {
      response = 'END Tech Foundations\n';
      response += 'Start here if you are new to code.\n';
      response += 'Visit academy.mctaba.com';
    } else {
      response = 'END Invalid selection.';
    }
  } else if (levels[0] === '2') {
    response = 'END We will call you shortly at ';
    response += phoneNumber;
    // TODO: queue a callback request
  } else if (levels[0] === '3') {
    response = 'END McTaba Labs\n';
    response += 'Nairobi\'s developer marathon.\n';
    response += 'mctaba.com';
  } else {
    response = 'END Invalid selection. Dial again.';
  }

  res.set('Content-Type', 'text/plain');
  res.send(response);
});

app.listen(3000, () =>
  console.log('USSD server running on port 3000')
);

Python (Flask):

from flask import Flask, request, make_response

app = Flask(__name__)

@app.route("/ussd", methods=["POST"])
def ussd():
    session_id = request.form.get("sessionId", "")
    phone = request.form.get("phoneNumber", "")
    text = request.form.get("text", "")

    levels = text.split("*") if text else []
    response = ""

    if len(levels) == 0:
        response = "CON Welcome to McTaba Labs\n"
        response += "1. Check courses\n"
        response += "2. Talk to a mentor\n"
        response += "3. About us"
    elif levels[0] == "1":
        if len(levels) == 1:
            response = "CON Available courses:\n"
            response += "1. Full-Stack AI Engineering\n"
            response += "2. M-Pesa Integration\n"
            response += "3. Tech Foundations"
        elif levels[1] == "1":
            response = "END Full-Stack AI Engineering\n"
            response += "26-week marathon.\n"
            response += "Visit mctaba.com for details."
        elif levels[1] == "2":
            response = "END M-Pesa Integration\n"
            response += "Learn Daraja API, STK Push, webhooks."
        elif levels[1] == "3":
            response = "END Tech Foundations\n"
            response += "Start here if you are new to code."
        else:
            response = "END Invalid selection."
    elif levels[0] == "2":
        response = f"END We will call you shortly at {phone}"
    elif levels[0] == "3":
        response = "END McTaba Labs\n"
        response += "Nairobi's developer marathon.\n"
        response += "mctaba.com"
    else:
        response = "END Invalid selection. Dial again."

    resp = make_response(response)
    resp.headers["Content-Type"] = "text/plain"
    return resp

if __name__ == "__main__":
    app.run(port=3000)

Testing with the Africa's Talking simulator

You do not need a real phone to test. Africa's Talking provides a browser-based phone simulator.

  1. Start your server locally and expose it via ngrok: ngrok http 3000
  2. Update your USSD channel callback URL on the Africa's Talking dashboard with the ngrok URL, e.g., https://abc123.ngrok-free.app/ussd
  3. Open the simulator on the Africa's Talking dashboard
  4. Enter a phone number (use the format +254712345678)
  5. Dial your USSD code (e.g., *384*123#)
  6. Interact with the menus just like you would on a real phone

The simulator shows you exactly what a real user would see on their phone screen. It is the fastest way to iterate on your menu design.

Debugging tip: log every incoming request (sessionId, text) and every response your server sends. USSD sessions are stateless from your server's perspective. The only state is the accumulated text field that Africa's Talking sends with each request. If your menus behave unexpectedly, print the text value and trace through your conditional logic.

Going to production with a real USSD code

To get a production USSD code in Kenya, you work with Africa's Talking (or another USSD gateway provider) and the mobile network operators.

The process:

  1. Contact Africa's Talking sales to request a production USSD code
  2. Choose between a dedicated shortcode (e.g., *123#) or a shared shortcode with an extension (e.g., *384*567#)
  3. Provide your business documentation (certificate of incorporation, KRA PIN)
  4. Africa's Talking handles the network operator approval process
  5. Approval typically takes 2 to 6 weeks depending on the operator

Dedicated shortcodes cost more but are easier for users to remember. Shared shortcodes are cheaper and work well for services where users are directed to the code (via SMS, website, or print) rather than remembering it.

Production considerations:

  • Session timeout: USSD sessions time out after about 30 seconds of inactivity. Keep your menus short and navigation simple.
  • Character limits: USSD screens can display about 160 characters. Longer text gets truncated on some phones.
  • Network differences: test on Safaricom, Airtel, and Telkom. Each network has slightly different USSD behavior.
  • Rate limits: Africa's Talking has rate limits on their API. Check their documentation for the current limits on your plan.

Frequently Asked Questions

How much does a USSD code cost in Kenya?
Sandbox is free. Production pricing depends on whether you want a dedicated or shared code, and the volume of sessions. [TODO: verify on provider website] Africa's Talking for current pricing. The cost has a monthly fee for the shortcode plus a per-session charge.
Can I trigger an M-Pesa payment from within a USSD session?
Not directly from the USSD session itself. However, you can collect the user's phone number and amount through the USSD menu, then initiate an STK Push from your server. The user will see the M-Pesa PIN prompt immediately after the USSD session. This is a common pattern for USSD-based payment flows.
Does USSD work across all mobile networks in Kenya?
USSD is a GSM standard supported by all networks (Safaricom, Airtel, Telkom). However, getting your shortcode activated on each network requires separate approval. Africa's Talking handles the multi-network provisioning for you, but it can take different amounts of time per network.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon. Ship 8 production apps with M-Pesa, USSD, and WhatsApp integrations, and get career support until placement.

See Programs

Also available: M-Pesa Integration course