Bonaventure OgetoBy Bonaventure Ogeto|

How USSD Apps Are Built: A Plain-Language Walkthrough

A USSD app works by connecting a shortcode (the *XXX# number) to a server through a telecom carrier or gateway like Africa's Talking. When a user dials the code, the carrier forwards the request to your server. Your server reads what the user typed, decides which menu to show next, and sends back plain text. The server uses CON to keep the session open or END to close it.

The Three Pieces of Every USSD App

Every USSD application has three parts, and once you understand them, the whole system clicks.

1. The shortcode. This is the number users dial. Something like *384*100# or *555#. You do not create this yourself. You get it from a telecom carrier (Safaricom, Airtel, Telkom) or more commonly from a USSD gateway provider like Africa's Talking. The shortcode is like a phone number for your application. When someone dials it, the network knows to route the request to your server.

2. The server. This is a web server you build and host. It has one job: receive requests from the USSD gateway and return text responses. When a user dials your shortcode, the gateway sends your server a message that says "this phone number just dialed your code, and here is what they typed so far." Your server decides what to show the user and sends back plain text.

3. The gateway. The gateway sits between the telecom network and your server. It translates the telecom protocol (which is complex and carrier-specific) into simple HTTP requests that your web server can understand. Africa's Talking is the most popular gateway for Kenyan developers. They handle all the telecom complexity. You just build a normal web endpoint.

Think of it like a restaurant. The shortcode is the restaurant's phone number. The gateway is the person who answers the phone and writes down orders. Your server is the kitchen that reads the order and prepares the food. The customer never talks to the kitchen directly. They talk to the person on the phone, who relays everything back and forth.

How a Session Flows, Step by Step

Let us walk through exactly what happens when a user dials your USSD code. We will use a simple example: a USSD app for a water delivery company in Nairobi.

Step 1: User dials *384*200#. The phone sends this code to the Safaricom network. Safaricom sees that this shortcode is registered with Africa's Talking and forwards the request. Africa's Talking translates it into an HTTP POST request and sends it to your server.

Step 2: Your server receives the first request. The POST request includes: the user's phone number (+254712345678), a session ID (unique for this session), the shortcode they dialed, and a text parameter that is empty (because the user has not typed anything yet, just dialed the code).

Step 3: Your server sends the main menu. Since text is empty, your code knows this is the start of a new session. You respond with:

CON Welcome to AquaDeliver
1. Order Water
2. Check Delivery Status
3. My Account

The CON prefix tells the system to keep the session open and wait for the user's response.

Step 4: User selects option 1. They type 1 and press send. Africa's Talking sends another POST request to your server. This time, text is "1".

Step 5: Your server shows the order menu. Your code checks that text equals "1" and responds with:

CON How many 20L jerricans?
1. One (KES 50)
2. Two (KES 90)
3. Five (KES 200)

Step 6: User selects option 2. They type 2. Africa's Talking sends another request. Now text is "1*2" (their first response, an asterisk separator, and their second response).

Step 7: Your server confirms and ends. Your code parses "1*2", knows the user wants to order two jerricans, and responds:

END Order placed: 2 jerricans (KES 90).
Delivery to your area within 2 hours.
You will receive an M-Pesa prompt shortly.

The END prefix closes the session. The user sees the confirmation and the session is done.

That is the entire flow. Your server reads the text parameter to figure out where the user is in the menu, then returns the appropriate text with either CON (continue) or END (finish).

The Text Parameter: Your Navigation Map

The text parameter is the key to understanding USSD development. It accumulates everything the user has typed during the session, with each response separated by an asterisk (*).

Here is what text looks like at each step of a session:

  • First screen (user just dialed): "" (empty string)
  • User types 1: "1"
  • User then types 2: "1*2"
  • User then types 0712345678: "1*2*0712345678"
  • User then types 1 to confirm: "1*2*0712345678*1"

Your server splits this string by the asterisk to figure out where the user is. The length of the resulting array tells you what "level" of the menu the user is on. The values tell you which options they picked.

For a basic menu, this is all you need. Split the text, check the length and values, and return the right response. For more complex apps with many branches, some developers use a state machine or a routing table instead of nested if-else blocks. But the principle is the same: read the text, decide the response.

One thing to watch for: the text parameter can contain anything the user types, not just menu numbers. If you ask "Enter your name" and the user types "John Kamau", that string goes into the text parameter, asterisk-separated from their previous inputs. Make sure your parsing handles free-text input correctly, especially if the user types an asterisk as part of their input (rare, but it happens).

What the Code Actually Looks Like

The server-side code for a USSD app is surprisingly small. Here is a complete, working example in Node.js with Express. This app lets users check water delivery rates and place orders.

const express = require('express');
const app = express();
app.use(express.urlencoded({ extended: false }));

app.post('/ussd', (req, res) => {
  const { sessionId, phoneNumber, text } = req.body;
  const inputs = text === '' ? [] : text.split('*');
  const level = inputs.length;
  let response = '';

  if (level === 0) {
    response = 'CON AquaDeliver Nairobi\n';
    response += '1. Order Water\n';
    response += '2. Check Rates\n';
    response += '3. Track Order';
  } else if (level === 1 && inputs[0] === '1') {
    response = 'CON How many 20L jerricans?\n';
    response += '1. One (KES 50)\n';
    response += '2. Two (KES 90)\n';
    response += '3. Five (KES 200)';
  } else if (level === 1 && inputs[0] === '2') {
    response = 'END Rates per 20L jerrican:\n';
    response += '1 jerrican: KES 50\n';
    response += '2 jerricans: KES 90\n';
    response += '5 jerricans: KES 200\n';
    response += 'Delivery free within Nairobi.';
  } else if (level === 2 && inputs[0] === '1') {
    const quantities = { '1': 1, '2': 2, '3': 5 };
    const prices = { '1': 50, '2': 90, '3': 200 };
    const qty = quantities[inputs[1]];
    const price = prices[inputs[1]];
    if (qty) {
      response = 'CON Order ' + qty + ' jerrican(s) for KES ' + price + '?\n';
      response += '1. Confirm\n';
      response += '2. Cancel';
    } else {
      response = 'END Invalid selection. Dial again to retry.';
    }
  } else if (level === 3 && inputs[0] === '1' && inputs[2] === '1') {
    response = 'END Order confirmed. You will receive an M-Pesa prompt shortly.';
    // Here you would trigger M-Pesa STK Push and save the order
  } else {
    response = 'END Invalid option. Dial *384*200# to try again.';
  }

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

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

That is under 50 lines. The entire application. No framework, no database (yet), no frontend build tools. Just a server that reads input and returns text. This is why USSD apps are one of the fastest backend projects you can build as a learning exercise.

In Python with Flask, the same logic is even shorter. In PHP, it is a single file. The language does not matter. The pattern is always: receive POST, parse text, return CON or END with your response.

Connecting Your USSD App to Real Systems

A menu that displays static text is a demo. A useful USSD app connects to databases, payment systems, and notification channels. Here is how the connections work in practice.

Database. When a user selects "Check Balance," your server queries your database using their phone number as the lookup key. The phone number comes in +254 format from Africa's Talking, but your database might store it as 0712 or 254712. Normalize the format before querying. This phone number normalization function will be one of the most used utilities in your codebase.

M-Pesa. When a user confirms a payment through your USSD menu, your server triggers an M-Pesa STK Push to their phone. The user sees the M-Pesa PIN prompt, enters their PIN, and the payment processes. Since the STK Push is asynchronous (the result comes via a callback to a separate URL), you end the USSD session immediately with "You will receive an M-Pesa prompt shortly" and handle the payment result separately.

SMS. USSD is good for input but limited for output. If you need to send the user a receipt, a reference number, or detailed information that does not fit in 182 characters, end the USSD session and trigger an SMS. Africa's Talking provides both USSD and SMS APIs, so you can send the SMS from the same server. This USSD-for-interaction, SMS-for-confirmation pattern is what M-Pesa itself uses.

Speed matters. If your server takes more than 2 seconds to respond (because of a slow database query or an external API call), the USSD session may time out. Keep your server fast. Cache frequently accessed data. If a database query might be slow, respond immediately with a "processing" message, end the session, and send the result via SMS when it is ready.

If you want hands-on experience building USSD and M-Pesa integrations, our Full-Stack Software and AI Engineering course (KES 120,000) covers the African Stack with real projects. You ship code that actually processes payments, not just sandbox exercises.

Key Takeaways

  • A USSD app is a server that receives text input from a phone and returns text menus. The entire "frontend" is plain text displayed on the phone's dialer screen.
  • The flow works like a conversation: user dials code, server sends menu, user picks option, server sends next menu. This repeats until the server sends an END response.
  • Africa's Talking is the most popular USSD gateway for Kenyan developers. They handle the telecom side, and you write a simple web server endpoint.
  • Menu design is the hardest part. You have 182 characters per screen, a 30-to-180-second timeout, and users who may be on tiny feature phone screens.

Frequently Asked Questions

What programming language do I need to build a USSD app?
Any language that can run a web server works. Node.js, Python (Flask or Django), PHP, Java, Ruby, Go. The USSD gateway sends HTTP POST requests and expects plain text responses. If you can build a simple API endpoint, you can build a USSD app. Most Kenyan developers use Node.js or Python because the Africa's Talking SDKs for those languages are well-maintained.
How do I test a USSD app without a real shortcode?
Africa's Talking provides a free sandbox with a test shortcode and a simulator. You register on their platform, set up a sandbox app, point it to your server URL (use ngrok to expose your local server), and test using their web-based USSD simulator. No real phone or shortcode needed during development.
How long does it take to build a basic USSD app?
A simple USSD menu with 3 to 5 features can be coded in a day. Setting up the Africa's Talking account and connecting it to your server takes another hour or two. The longer work is designing good menus, connecting to your database and payment systems, and testing thoroughly. End to end, a production-ready USSD application typically takes 1 to 2 weeks for a developer who has not built one before.
Can a USSD app handle many users at once?
Yes. Each USSD session is an independent HTTP request. A properly configured web server can handle hundreds or thousands of concurrent sessions. The bottleneck is usually your database or external API calls, not the USSD handling itself. Use connection pooling for your database and keep your response logic fast. If you expect high traffic, load test before going live.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon