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

  1. Initiation: User dials USSD code (e.g., *123#)
  2. Request: Network sends request to your relay endpoint
  3. Processing: Your application processes the request
  4. Response: Your application returns a menu or prompt
  5. Interaction: User selects option or enters data
  6. Loop: Steps 2-5 repeat until session ends
  7. 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"
  }
}

Building intuitive menu structures is key to good USSD UX.

Menu Best Practices

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

Performance

Security

Testing

✅ Ready to Build? Contact us at info@sms.com.na to get your USSD short code and start building!