USSD API Documentation
Base URL: https://ussd.sms.com.na
The USSD API enables you to build interactive USSD applications with session management, menu navigation, and real-time user interactions.
What is USSD? Unstructured Supplementary Service Data (USSD) is a protocol used by GSM
cellular phones to communicate with mobile network operators. It enables interactive menu-driven
services.
Authentication
All API requests require authentication using an API key in the Authorization header:
Authorization: App YOUR_API_KEY
Security: Keep your API key secure. Never expose it in client-side code or public
repositories.
USSD Flow
Understanding the USSD session lifecycle is crucial for building effective USSD applications.
Session Lifecycle
- Initiation: User dials USSD code (e.g., *123#)
- Request: Network sends request to your relay endpoint
- Processing: Your application processes the request
- Response: Your application returns a menu or prompt
- Interaction: User selects option or enters data
- Loop: Steps 2-5 repeat until session ends
- Termination: Session ends (user cancels or application terminates)
Session Timeout: USSD sessions automatically timeout after 180 seconds of inactivity.
USSD Relay Endpoint
POST /ussd/relay/{your-service-code}
This is your webhook endpoint that receives USSD requests from the network.
Request Headers
| Header | Value | Description |
|---|---|---|
Content-Type |
application/json | Request body format |
X-Session-Id |
Unique session ID | Identifies the USSD session |
Request Body
{
"sessionId": "sess-12345678-1234-1234-1234-123456789012",
"msisdn": "264811234567",
"ussdCode": "*123#",
"userInput": "",
"sessionState": "BEGIN",
"network": "MTC",
"timestamp": "2026-06-25T10:30:00.000Z"
}
Request Parameters
| Parameter | Type | Description |
|---|---|---|
sessionId |
string | Unique identifier for the USSD session |
msisdn |
string | User's phone number (E.164 format) |
ussdCode |
string | The USSD code dialed by the user |
userInput |
string | User's input from previous menu (empty on first request) |
sessionState |
string | BEGIN, CONTINUE, or END |
network |
string | Mobile network operator (MTC, TN Mobile, etc.) |
timestamp |
string | ISO 8601 timestamp of the request |
Response Format
Your endpoint must return a JSON response with the following structure:
{
"message": "Welcome to MyService\n1. Check Balance\n2. Buy Airtime\n3. Help",
"continueSession": true
}
Response Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
message |
string | Yes | Text to display to user (max 182 characters) |
continueSession |
boolean | Yes | true = wait for user input, false = end session |
Session Management
Proper session management is critical for USSD applications.
Session States
| State | Description | When It Occurs |
|---|---|---|
BEGIN |
New session started | User dials USSD code for the first time |
CONTINUE |
Session in progress | User provides input to continue interaction |
END |
Session terminated | User cancels or application ends session |
Storing Session Data
You should store session data on your server using the sessionId as the key:
// Example session storage structure
{
"sess-12345": {
"msisdn": "264811234567",
"currentMenu": "main",
"userData": {
"accountNumber": "123456",
"selectedOption": "1"
},
"createdAt": "2026-06-25T10:30:00.000Z",
"lastActivity": "2026-06-25T10:30:15.000Z"
}
}
Menu Navigation
Building intuitive menu structures is key to good USSD UX.
Menu Best Practices
- Keep menus simple with 3-5 options maximum
- Use clear, concise language
- Number options sequentially (1, 2, 3...)
- Always provide a "Back" or "Home" option
- Include "0" for main menu navigation
Example Menu Structure
Main Menu:
Welcome to MyService
1. Check Balance
2. Buy Airtime
3. Transfer Money
0. Exit
Sub-Menu (Option 2):
Buy Airtime
1. N$10
2. N$20
3. N$50
0. Back
Handling User Input
// Pseudo-code for menu navigation
if (sessionState === "BEGIN") {
return showMainMenu();
}
if (currentMenu === "main") {
switch (userInput) {
case "1":
return showBalance();
case "2":
return showAirtimeMenu();
case "3":
return showTransferMenu();
case "0":
return endSession("Thank you!");
default:
return showError("Invalid option. Try again.");
}
}
Response Types
Continue Session (Show Menu)
{
"message": "Select an option:\n1. Option A\n2. Option B\n0. Exit",
"continueSession": true
}
End Session (Final Message)
{
"message": "Thank you for using our service!",
"continueSession": false
}
Request Input
{
"message": "Enter your account number:",
"continueSession": true
}
Confirmation Message
{
"message": "Your balance is N$150.00",
"continueSession": false
}
Error Handling
Common Errors
| Error | Cause | Solution |
|---|---|---|
| Session Timeout | No activity for 180 seconds | Inform user and restart session |
| Invalid Input | User enters unexpected value | Show error and re-prompt |
| Service Unavailable | Backend service down | Show friendly error message |
Error Response Example
{
"message": "Invalid option. Please try again.\n1. Option A\n2. Option B",
"continueSession": true
}
Code Examples
Node.js/Express Example
const express = require('express');
const app = express();
app.use(express.json());
// Session storage (use Redis in production)
const sessions = {};
app.post('/ussd/relay/myservice', (req, res) => {
const { sessionId, msisdn, userInput, sessionState } = req.body;
// Initialize session
if (sessionState === 'BEGIN') {
sessions[sessionId] = {
msisdn,
currentMenu: 'main',
createdAt: new Date()
};
return res.json({
message: 'Welcome to MyService\n1. Check Balance\n2. Buy Airtime\n0. Exit',
continueSession: true
});
}
// Get session
const session = sessions[sessionId];
if (!session) {
return res.json({
message: 'Session expired. Please try again.',
continueSession: false
});
}
// Handle main menu
if (session.currentMenu === 'main') {
switch (userInput) {
case '1':
return res.json({
message: 'Your balance is N$150.00',
continueSession: false
});
case '2':
session.currentMenu = 'airtime';
return res.json({
message: 'Buy Airtime\n1. N$10\n2. N$20\n3. N$50\n0. Back',
continueSession: true
});
case '0':
delete sessions[sessionId];
return res.json({
message: 'Thank you!',
continueSession: false
});
default:
return res.json({
message: 'Invalid option. Try again.\n1. Check Balance\n2. Buy Airtime\n0. Exit',
continueSession: true
});
}
}
// Handle airtime menu
if (session.currentMenu === 'airtime') {
if (userInput === '0') {
session.currentMenu = 'main';
return res.json({
message: 'Welcome to MyService\n1. Check Balance\n2. Buy Airtime\n0. Exit',
continueSession: true
});
}
const amounts = { '1': 10, '2': 20, '3': 50 };
const amount = amounts[userInput];
if (amount) {
delete sessions[sessionId];
return res.json({
message: `N$${amount} airtime purchased successfully!`,
continueSession: false
});
}
return res.json({
message: 'Invalid amount. Try again.\n1. N$10\n2. N$20\n3. N$50\n0. Back',
continueSession: true
});
}
});
app.listen(3000, () => console.log('USSD service running on port 3000'));
Python/Flask Example
from flask import Flask, request, jsonify
from datetime import datetime
app = Flask(__name__)
sessions = {}
@app.route('/ussd/relay/myservice', methods=['POST'])
def ussd_handler():
data = request.json
session_id = data['sessionId']
msisdn = data['msisdn']
user_input = data['userInput']
session_state = data['sessionState']
# Initialize session
if session_state == 'BEGIN':
sessions[session_id] = {
'msisdn': msisdn,
'currentMenu': 'main',
'createdAt': datetime.now()
}
return jsonify({
'message': 'Welcome to MyService\n1. Check Balance\n2. Buy Airtime\n0. Exit',
'continueSession': True
})
# Get session
session = sessions.get(session_id)
if not session:
return jsonify({
'message': 'Session expired. Please try again.',
'continueSession': False
})
# Handle main menu
if session['currentMenu'] == 'main':
if user_input == '1':
return jsonify({
'message': 'Your balance is N$150.00',
'continueSession': False
})
elif user_input == '2':
session['currentMenu'] = 'airtime'
return jsonify({
'message': 'Buy Airtime\n1. N$10\n2. N$20\n3. N$50\n0. Back',
'continueSession': True
})
elif user_input == '0':
del sessions[session_id]
return jsonify({
'message': 'Thank you!',
'continueSession': False
})
else:
return jsonify({
'message': 'Invalid option. Try again.\n1. Check Balance\n2. Buy Airtime\n0. Exit',
'continueSession': True
})
if __name__ == '__main__':
app.run(port=3000)
PHP Example
<?php
session_start();
$input = json_decode(file_get_contents('php://input'), true);
$sessionId = $input['sessionId'];
$msisdn = $input['msisdn'];
$userInput = $input['userInput'];
$sessionState = $input['sessionState'];
// Initialize session
if ($sessionState === 'BEGIN') {
$_SESSION[$sessionId] = [
'msisdn' => $msisdn,
'currentMenu' => 'main'
];
echo json_encode([
'message' => "Welcome to MyService\n1. Check Balance\n2. Buy Airtime\n0. Exit",
'continueSession' => true
]);
exit;
}
// Get session
$session = $_SESSION[$sessionId] ?? null;
if (!$session) {
echo json_encode([
'message' => 'Session expired. Please try again.',
'continueSession' => false
]);
exit;
}
// Handle main menu
if ($session['currentMenu'] === 'main') {
switch ($userInput) {
case '1':
echo json_encode([
'message' => 'Your balance is N$150.00',
'continueSession' => false
]);
break;
case '2':
$_SESSION[$sessionId]['currentMenu'] = 'airtime';
echo json_encode([
'message' => "Buy Airtime\n1. N\$10\n2. N\$20\n3. N\$50\n0. Back",
'continueSession' => true
]);
break;
case '0':
unset($_SESSION[$sessionId]);
echo json_encode([
'message' => 'Thank you!',
'continueSession' => false
]);
break;
default:
echo json_encode([
'message' => "Invalid option. Try again.\n1. Check Balance\n2. Buy Airtime\n0. Exit",
'continueSession' => true
]);
}
}
?>
Best Practices
User Experience
- Keep it simple: Limit menu options to 3-5 items
- Be concise: Messages should be under 160 characters when possible
- Provide feedback: Confirm actions with clear messages
- Handle errors gracefully: Always validate input and provide helpful error messages
- Timeout warnings: Warn users before session timeout
Performance
- Fast responses: Respond within 2 seconds
- Cache data: Store frequently accessed data in memory
- Async operations: Use background jobs for heavy processing
- Session cleanup: Remove expired sessions regularly
Security
- Validate input: Always sanitize and validate user input
- Rate limiting: Prevent abuse with rate limits
- Secure storage: Encrypt sensitive session data
- Audit logs: Log all transactions for compliance
Testing
- Test all paths: Cover all menu options and edge cases
- Simulate timeouts: Test session timeout handling
- Load testing: Ensure your service can handle peak traffic
- User testing: Get feedback from real users
✅ Ready to Build? Contact us at info@sms.com.na to get your USSD short code and start building!