USSD API
Build interactive mobile applications using USSD (Unstructured Supplementary Service Data) to reach users without internet connectivity. Perfect for financial services, surveys, mobile services, and menu-driven applications across Africa.
📱 Overview
USSD enables real-time, session-based interactions with mobile users through simple menu interfaces. No internet or app installation required - works on any phone.
Key Features
- 🔄 Session Management - Maintain state across multi-step interactions
- 📊 Menu Builder - Create dynamic, multilevel menus
- 🌍 Pan-African Coverage - Available in 50+ African countries
- ⚡ Real-time Response - Instant user interactions
- 🔐 Secure Sessions - Encrypted session data
- 📈 Analytics - Track usage, completion rates, and user flows
Use Cases
- Mobile Banking - Balance checks, transfers, bill payments
- Surveys & Polls - Quick data collection and feedback
- Service Menus - Customer support, account management
- Agent Applications - Field agent data collection
- Voting Systems - Secure voting and polling
- Ticketing - Event tickets, transport bookings
🚀 Quick Start
Send a USSD Menu
POST https://api.afriroute.ai/api/v1/ussd/send
{
"phone": "+251911234567",
"shortcode": "*123*456#",
"message": "Welcome to AfriRoute!\n1. Check Balance\n2. Send Money\n3. Buy Airtime\n4. Help",
"session_id": "unique-session-id-12345"
}
Response
{
"status": "success",
"message_id": "msg_7K8L9M0N1P2Q",
"session_id": "unique-session-id-12345",
"phone": "+251911234567",
"cost": 0.005,
"timestamp": "2024-03-15T10:30:00Z"
}
📡 API Endpoints
1. Send USSD Menu
Send a USSD menu to a user and initiate a session.
POST /v1/ussd/send
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
phone | string | Yes | Recipient phone number in E.164 format |
shortcode | string | Yes | USSD shortcode (e.g., *123# or *123*456#) |
message | string | Yes | Menu text to display (max 182 characters) |
session_id | string | Yes | Unique session identifier |
data | object | No | Custom session data to store |
timeout | integer | No | Session timeout in seconds (default: 90) |
Example Request
const response = await fetch('https://api.afriroute.ai/api/v1/ussd/send', {
method: 'POST',
headers: {
'Authorization': 'Bearer $AFRIROUTE_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone: '+251911234567',
shortcode: '*123#',
message: 'Welcome to MyBank\n1. Balance\n2. Transfer\n3. Loans\n0. Exit',
session_id: crypto.randomUUID(),
data: {
user_id: 'user_12345',
language: 'en'
}
})
});
import requests
response = requests.post(
'https://api.afriroute.ai/api/v1/ussd/send',
headers={
'Authorization': 'Bearer $AFRIROUTE_API_KEY',
'Content-Type': 'application/json'
},
json={
'phone': '+251911234567',
'shortcode': '*123#',
'message': 'Welcome to MyBank\n1. Balance\n2. Transfer\n3. Loans\n0. Exit',
'session_id': 'unique-session-123',
'data': {
'user_id': 'user_12345',
'language': 'en'
}
}
)
2. Handle User Response
Process user input and continue the USSD session.
POST /v1/ussd/continue
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | Yes | Session identifier from previous step |
input | string | Yes | User's input/selection |
message | string | Yes | Next menu or response to show |
end_session | boolean | No | Whether to end the session (default: false) |
Example: Multi-Step USSD Flow
// Step 1: Initial Menu
const step1 = await sendUSSD({
phone: '+251911234567',
shortcode: '*123#',
message: 'Select Service:\n1. Balance\n2. Transfer\n3. Airtime',
session_id: 'session_abc123'
});
// Step 2: User selects "2" (Transfer)
const step2 = await continueUSSD({
session_id: 'session_abc123',
input: '2',
message: 'Enter recipient phone:'
});
// Step 3: User enters phone number
const step3 = await continueUSSD({
session_id: 'session_abc123',
input: '+251922334455',
message: 'Enter amount:'
});
// Step 4: User enters amount
const step4 = await continueUSSD({
session_id: 'session_abc123',
input: '100',
message: 'Confirm transfer of 100 ETB to +251922334455?\n1. Yes\n2. No'
});
// Step 5: User confirms
const step5 = await continueUSSD({
session_id: 'session_abc123',
input: '1',
message: 'Transfer successful! Your new balance is 450 ETB.',
end_session: true
});
3. Get Session Data
Retrieve stored session data.
GET /v1/ussd/sessions/:session_id
Response
{
"session_id": "session_abc123",
"phone": "+251911234567",
"shortcode": "*123#",
"status": "active",
"created_at": "2024-03-15T10:30:00Z",
"last_activity": "2024-03-15T10:32:15Z",
"expires_at": "2024-03-15T10:33:30Z",
"data": {
"user_id": "user_12345",
"current_step": "confirm_transfer",
"transfer_amount": 100,
"recipient": "+251922334455"
},
"history": [
{"step": 1, "input": null, "output": "Select Service..."},
{"step": 2, "input": "2", "output": "Enter recipient phone:"},
{"step": 3, "input": "+251922334455", "output": "Enter amount:"},
{"step": 4, "input": "100", "output": "Confirm transfer..."}
]
}
4. End Session
Manually end an active USSD session.
POST /v1/ussd/sessions/:session_id/end
🔄 Session Management
Session Lifecycle
- Initiated - User dials shortcode, session created
- Active - User interacting with menus
- Expired - No activity for timeout period
- Completed - User exits or flow finishes
Session Storage
Store custom data throughout the session:
// Store data during session
await continueUSSD({
session_id: 'session_123',
input: '2',
message: 'Enter PIN:',
data: {
selected_service: 'transfer',
step: 2,
metadata: {
start_time: Date.now()
}
}
});
// Retrieve later
const session = await getSession('session_123');
console.log(session.data.selected_service); // 'transfer'
Timeout Handling
// Set custom timeout (default: 90 seconds)
await sendUSSD({
phone: '+251911234567',
shortcode: '*123#',
message: 'Welcome! This session expires in 2 minutes.',
session_id: 'session_123',
timeout: 120 // 2 minutes
});
// Handle timeout webhook
app.post('/ussd/timeout', (req, res) => {
const { session_id, data } = req.body;
// Clean up or save partial data
console.log(`Session ${session_id} timed out`);
res.json({ status: 'acknowledged' });
});
📋 Menu Design Best Practices
1. Keep It Simple
// ✅ Good - Clear and concise
const menu = `Welcome to MyService
1. Check Balance
2. Buy Airtime
3. Help
0. Exit`;
// ❌ Bad - Too much text
const badMenu = `Welcome to MyService. We provide various services including...`;
2. Use Consistent Navigation
// Always use consistent patterns
const menuPattern = {
home: '0', // Return to main menu
back: '00', // Go back one step
exit: '000' // Exit session
};
3. Provide Clear Instructions
const menu = `Enter phone number:
(e.g., 0911234567)
00. Back 0. Home`;
4. Handle Invalid Input
function handleInput(input, session) {
if (!isValidSelection(input)) {
return {
message: 'Invalid selection. Please try again.\n1. Balance\n2. Transfer\n0. Exit',
data: session.data // Preserve session state
};
}
// Process valid input
return processSelection(input, session);
}
🌍 Country Coverage
USSD is available in these countries:
| Country | Shortcode Format | Operators |
|---|---|---|
| 🇪🇹 Ethiopia | *123#, *123*4# | Ethio Telecom |
| 🇰🇪 Kenya | *123# | Safaricom, Airtel |
| 🇹🇿 Tanzania | *150*00# | Vodacom, Tigo, Airtel |
| 🇺🇬 Uganda | *165# | MTN, Airtel |
| 🇳🇬 Nigeria | *123# | MTN, Glo, Airtel, 9mobile |
| 🇬🇭 Ghana | *170# | MTN, Vodafone, AirtelTigo |
| 🇿🇦 South Africa | *120# | All major operators |
🔔 Webhooks
Configure webhooks to receive real-time notifications:
User Response Webhook
POST https://your-server.com/ussd/response
{
"event": "ussd.response",
"session_id": "session_123",
"phone": "+251911234567",
"input": "2",
"timestamp": "2024-03-15T10:32:15Z",
"data": {
"current_step": 2
}
}
Session Ended Webhook
{
"event": "ussd.session_ended",
"session_id": "session_123",
"phone": "+251911234567",
"reason": "completed",
"duration": 45,
"total_steps": 5,
"timestamp": "2024-03-15T10:33:00Z"
}
💡 Advanced Examples
Banking Application
class USSDBank {
async handleMenu(sessionId, input) {
const session = await getSession(sessionId);
switch(session.data.step) {
case 'main':
return this.mainMenu();
case 'balance':
return this.checkBalance(session.data.accountId);
case 'transfer_phone':
return this.collectRecipient(input);
case 'transfer_amount':
return this.collectAmount(input);
case 'transfer_confirm':
return this.confirmTransfer(input, session);
default:
return this.mainMenu();
}
}
mainMenu() {
return {
message: 'MyBank USSD\n1. Balance\n2. Transfer\n3. Mini Statement\n4. Airtime\n0. Exit',
data: { step: 'main' }
};
}
async checkBalance(accountId) {
const balance = await getAccountBalance(accountId);
return {
message: `Your balance is: ${balance} ETB\n\n00. Main Menu\n0. Exit`,
end_session: true
};
}
}
Survey Application
class USSDSurvey {
async handleSurvey(sessionId, input) {
const session = await getSession(sessionId);
const questions = this.getQuestions();
const currentQ = session.data.currentQuestion || 0;
// Save answer
if (input && currentQ > 0) {
await this.saveAnswer(sessionId, currentQ - 1, input);
}
// Check if survey complete
if (currentQ >= questions.length) {
return {
message: 'Thank you for completing our survey!',
end_session: true
};
}
// Show next question
const question = questions[currentQ];
return {
message: `Question ${currentQ + 1}/${questions.length}\n${question.text}\n${question.options}`,
data: {
currentQuestion: currentQ + 1
}
};
}
getQuestions() {
return [
{
text: 'How satisfied are you with our service?',
options: '1. Very Satisfied\n2. Satisfied\n3. Neutral\n4. Dissatisfied\n5. Very Dissatisfied'
},
{
text: 'Would you recommend us?',
options: '1. Yes\n2. No'
}
];
}
}
🔒 Security
PIN Verification
async function verifyPIN(sessionId, pin) {
const session = await getSession(sessionId);
const userId = session.data.userId;
const isValid = await validateUserPIN(userId, pin);
if (!isValid) {
session.data.pinAttempts = (session.data.pinAttempts || 0) + 1;
if (session.data.pinAttempts >= 3) {
return {
message: 'Too many failed attempts. Account locked.',
end_session: true
};
}
return {
message: `Incorrect PIN. ${3 - session.data.pinAttempts} attempts remaining.\nEnter PIN:`,
data: session.data
};
}
return { verified: true };
}
Encryption
All session data is encrypted at rest and in transit:
// Data is automatically encrypted
await continueUSSD({
session_id: 'session_123',
message: 'Enter PIN:',
data: {
accountNumber: '1234567890', // Encrypted automatically
sensitiveData: 'secret'
}
});
📊 Analytics
Track USSD performance and user behavior:
GET /v1/ussd/analytics
Metrics Available
- Session Count - Total sessions initiated
- Completion Rate - % of sessions completed
- Average Duration - Mean session length
- Drop-off Points - Where users exit
- Popular Flows - Most used menu paths
- Error Rate - Failed interactions
Example Dashboard Query
const analytics = await fetch('https://api.afriroute.ai/api/v1/ussd/analytics', {
headers: { 'Authorization': 'Bearer $AFRIROUTE_API_KEY' },
params: {
start_date: '2024-03-01',
end_date: '2024-03-31',
shortcode: '*123#',
metrics: ['sessions', 'completion_rate', 'avg_duration']
}
});
// Response
{
"period": "2024-03-01 to 2024-03-31",
"shortcode": "*123#",
"sessions": 15420,
"completion_rate": 78.5,
"avg_duration": 42.3,
"popular_paths": [
{ "path": "main → balance", "count": 5240 },
{ "path": "main → transfer", "count": 3890 }
]
}
💰 Pricing
| Tier | Price per Session | Included Sessions | Overages |
|---|---|---|---|
| Starter | $0.008 | 5,000 | $0.010 |
| Growth | $0.006 | 50,000 | $0.008 |
| Business | $0.004 | 250,000 | $0.006 |
| Enterprise | Custom | Unlimited | N/A |
Volume Discounts: Save up to 50% on high-volume plans.
🛠️ SDKs
# Node.js
npm install @afriroute/ussd
# Python
pip install afriroute-ussd
# PHP
composer require afriroute/ussd-sdk
# Java
maven: com.afriroute:ussd-sdk
📚 Related Resources
❓ FAQ
What's the maximum message length?
USSD messages can be up to 182 characters. For longer content, break it into multiple screens.
How long do sessions last?
Default session timeout is 90 seconds, configurable up to 180 seconds.
Can I use USSD for payments?
Yes! USSD is commonly used for mobile money transfers, bill payments, and airtime purchases.
Do I need a dedicated shortcode?
No, you can use shared shortcodes to get started. Dedicated shortcodes are available for enterprise customers.
How do I test USSD without a phone?
Use our USSD simulator in the dashboard or our testing API endpoints.
🚀 Get Started
# Test with cURL
curl -X POST https://api.afriroute.ai/api/v1/ussd/send \
-H "Authorization: Bearer $AFRIROUTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+251911234567",
"shortcode": "*123#",
"message": "Welcome!\n1. Get Started\n2. Help",
"session_id": "test_session_123"
}'
Get API Keys → | View Code Examples → | Contact Support →
💬 Support
- Email: [email protected]
- Slack: Join our workspace
- Phone: +251-11-XXX-XXXX (24/7)
- Documentation: docs.afriroute.ai
Need help? We're here 24/7! 🚀