If you've ever dialed *170# to check a mobile money balance, or *123# to buy airtime, you've used USSD — quite possibly the single most-used piece of financial technology on the African continent, despite being older than the smartphone.
This guide covers what USSD actually is, how it works under the hood, and why it remains the default channel for mobile money and banking across markets where internet access still isn't universal.
USSD (Unstructured Supplementary Service Data) is a communication protocol built into the GSM mobile network standard that lets a phone exchange short messages with an application in real time, over the same signaling channel used for calls — not over the internet.
When you dial a USSD code like *123#, your phone opens a live session with a server, which responds with a text menu you navigate by typing numbers. Unlike SMS, which sends and receives standalone messages, a USSD session is a continuous back-and-forth conversation that stays open until the user finishes or it times out.
Three properties explain why USSD has outlasted multiple generations of "the next big thing" in mobile:
USSD runs over the GSM signaling channel, the same one used for voice calls — it needs no data connection and works on the most basic feature phone ever sold. In markets where mobile data is expensive or coverage is patchy, this alone makes USSD the only channel that reliably reaches everyone.
Unlike SMS, a USSD session is live and interactive — the user sees a menu, picks an option, and gets an immediate response, all within a single continuous session. That real-time back-and-forth is what makes USSD suitable for things SMS can't do well, like multi-step transactions.
Mobile money services across Africa are built on USSD precisely because it reaches every phone, requires no app install, and completes transactions in seconds. For a huge share of the population, USSD banking menus are their primary banking interface.
*384*1#.CON vs END below).[ User dials *384*1# ]
│
▼
[ Carrier routes session to your USSD gateway ]
│
▼
[ Your server responds with menu: "CON Welcome. 1. Balance 2. Transfer" ]
│
▼
[ User selects "1" ]
│
▼
[ Your server responds: "END Your balance is GHS 500.00" ]
Every response your application sends back has to be prefixed with one of two keywords, and this single detail is what makes a USSD menu actually navigable:
CON — "continue": the session stays open and the carrier displays your message, waiting for further input.END — the session terminates after displaying your message; no more input is expected.Get this wrong — send END when you meant CON — and the user's session closes after step one, no matter how many menu levels you built.
These two get confused constantly since both work on any phone without data:
| USSD | SMS | |
|---|---|---|
| Connection type | Live session, stays open | Individual messages, no persistent session |
| Speed | Real-time, sub-second responses | Seconds to minutes, depending on carrier |
| Best for | Interactive menus, transactions, multi-step flows | One-way alerts, OTPs, notifications |
| Session length | Typically 60–120 seconds, then times out | No session — each message stands alone |
| Cost model | Usually billed per session | Usually billed per message/segment |
| Record kept on phone | No message history after the session ends | Persists in the user's inbox |
In practice, most fintech products use both: USSD for the live transaction (checking balance, sending money), SMS for the confirmation receipt afterward.
Here's a minimal webhook handler showing the actual request/response shape a USSD gateway sends:
import express from "express";
const app = express();
app.use(express.urlencoded({ extended: true }));
app.post("/ussd", (req, res) => {
const { sessionId, serviceCode, phoneNumber, text } = req.body;
let response = "";
if (text === "") {
// First request in the session — show the main menu
response = `CON Welcome to Sendexa Demo
1. Check Balance
2. Send Money`;
} else if (text === "1") {
response = `END Your balance is GHS 500.00`;
} else if (text === "2") {
response = `CON Enter amount to send:`;
} else if (text.startsWith("2*")) {
const amount = text.split("*")[1];
response = `END GHS ${amount} has been sent.`;
} else {
response = `END Invalid option.`;
}
res.set("Content-Type", "text/plain");
res.status(200).send(response);
});
app.listen(3000, () => console.log("USSD service listening on port 3000"));
text accumulates the user's choices across the session, separated by * — that's how a stateless webhook can still track a multi-step menu without a database, for simple flows. For anything beyond a couple of levels, you'll want to persist session state server-side rather than parsing an ever-growing text string.
For a full walkthrough of getting a short code, going live, and handling session state properly, see our step-by-step USSD build tutorial.
USSD isn't legacy technology being kept alive out of nostalgia — it's the only channel that reliably reaches every phone on the network, with zero dependency on internet access or app installs, and near-instant response times. For any product that needs to reach users beyond the smartphone-and-data-plan segment — which, across most of Africa, is still the majority — USSD is not optional infrastructure.
Sendexa provides USSD gateway access with direct connections to major carriers, short code provisioning, session management, and mobile money integration, so you can focus on the menu logic instead of the carrier plumbing.
