USSD application development workflow showing a Python code editor with USSD callback logic connected via API to a mobile phone displaying a USSD menu with Ghana carrier badges

How to Create a USSD Code: Developer Guide for Africa

How to Create a USSD Code: What Is USSD and How Does It Work?

USSD (Unstructured Supplementary Service Data) is a real-time, session-based communication protocol built into every GSM network. Dial *920*100# on any phone and you’re using it.

Unlike SMS, USSD creates a live session between the user’s handset and your application server. That session stays open for the entire interaction — no internet required, no app download needed, works on every phone ever made.

A significant share of mobile users across Africa still rely on feature phones. USSD reaches all of them — making USSD application development a strategic investment for businesses in Ghana. That makes it the backbone of interactive customer experiences across Africa — from mobile money to self-service portals.

The protocol follows a straightforward request-response model:

  1. User dials a USSD code (e.g., *384*1234#)
  2. The mobile network routes the request to a USSD gateway
  3. The gateway forwards it to your application server via HTTP
  4. Your server processes the request and returns a menu or response
  5. The user responds, and the cycle continues until the session ends

Each session typically lasts up to 180 seconds. Within that window, you can build multi-step flows — account lookups, payments, surveys, registration forms — all through numbered text menus.

USSD Gateway Architecture: How the Pieces Connect

USSD gateway architecture diagram showing the four-layer flow from user phone through telco network and USSD gateway to application server, with session lifecycle, callback payload, and key specs

Before writing your first line of code, understand the three layers that make a USSD application work. Every USSD gateway routes requests through this same architecture.

Layer 1: The Telco Network

Mobile network operators (MTN Ghana, Telecel Ghana, AirtelTigo) own the USSD infrastructure. When a subscriber dials your shortcode, their network’s USSD gateway handles the signaling. You don’t interact with this layer directly.

Layer 2: The USSD Gateway (Aggregator)

USSD gateway providers like Arkesel sit between the telco and your application. They handle shortcode provisioning, telco integrations, and session routing across multiple carriers.

When a user dials your code, the USSD gateway translates the telco’s signaling protocol into a clean HTTP POST to your server. With the right gateway provider, you skip months of carrier negotiations and complex protocol work.

Layer 3: Your Application Server

Your server receives HTTP POST requests from the USSD API, processes input, manages session state, and returns text responses. You control the menu logic, data access, and business rules. This is where you build.

The architecture flow:

User's Phone → Telco Network → USSD Gateway (Arkesel) → Your Server
     ↑                                                        |
     └──────────── Response (text menu) ───────────────────────┘

Choosing a USSD Provider in Ghana

Selecting the right USSD gateway provider determines how fast you go live and how reliably your application performs across carriers. With several USSD providers in Ghana offering different capabilities, here’s what to evaluate.

Shared vs Dedicated Shortcodes

A shared shortcode (e.g., *384*100#) lets multiple businesses share one USSD code with unique extensions. Lower cost, faster setup — ideal for development and early-stage products.

A dedicated shortcode (e.g., *920#) is exclusively yours. Premium branding, but requires NCA (National Communications Authority) registration in Ghana. Timeline: 2–4 weeks.

For a deeper walkthrough on shortcode options, see our guide on how to get a USSD shortcode in Ghana.

Multi-Network Coverage

Ghana has three major mobile networks: MTN Ghana, Telecel Ghana (formerly Vodafone Ghana), and AirtelTigo. Your USSD gateway provider must route sessions across all three — or you lose reach to a significant slice of your user base.

Arkesel maintains direct connections to all three Ghanaian carriers, so a single USSD API integration covers the entire market.

API-First vs No-Code Platforms

If you’re a developer, you want an API-first USSD gateway — webhook-based session routing, JSON payloads, and full control over your menu logic. That’s the approach this guide covers.

If your team doesn’t have developers, some USSD providers offer drag-and-drop menu builders that let you launch without writing code. Arkesel supports both paths — the USSD API for developers and managed setup for non-technical teams.

What Else to Look For

  • Session routing architecture — webhook-based callbacks let you host logic on your own infrastructure
  • Transparent pricing — check current USSD pricing before committing
  • Developer documentation — clear API docs, sandbox environment, and code examples accelerate integration
  • Uptime and reliability — for financial services and critical flows, 99.9% uptime matters

Ready to build? Create your Arkesel developer account and get your USSD API key in minutes.

Step-by-Step: How to Build a USSD Application with the Arkesel USSD API

This section walks you through the complete USSD API integration — from account setup to a working application that handles real user sessions.

Step 1: Set Up Your Arkesel Developer Account

  1. Create an account at account.arkesel.com/signup
  2. Navigate to the USSD section in your dashboard
  3. Generate your API key (you’ll need this for authentication)
  4. Set your callback URL — this is the endpoint Arkesel’s USSD API will POST to when users interact with your code

Step 2: Configure Your USSD Shortcode

You have two options for getting a USSD shortcode:

  • Shared shortcode: A code shared with other businesses (e.g., *384*100#). Lower cost, faster setup.
  • Dedicated shortcode: Your own exclusive code (e.g., *384*1234#). Premium branding, requires NCA registration in Ghana.

For development and testing, start with a shared shortcode. You can upgrade to a dedicated code once your application is live and gaining traction.

Step 3: Understand the USSD API Callback Payload

When a user interacts with your USSD code, Arkesel’s USSD API sends a POST request to your callback URL with these parameters:

{
  "sessionId": "a-unique-session-identifier",
  "serviceCode": "*384*1234#",
  "phoneNumber": "+233241234567",
  "text": "",
  "type": "initiation"
}

Key fields:

  • sessionId — Unique identifier for this USSD session. Use it to track state across multiple interactions.
  • serviceCode — The USSD code the user dialed.
  • phoneNumber — The subscriber’s number (MSISDN format).
  • text — The user’s accumulated input. An empty string on first request. On subsequent interactions, inputs are concatenated with * (e.g., "1*2*0241234567").
  • type"initiation" for new sessions, "response" for subsequent interactions.

Note: Always reference the Arkesel developer documentation for the latest payload format and field definitions.

Step 4: Build Your Response Logic

Your server must return a plain text response. Two response types:

  • Continue session: Prefix with CON — shows a menu and waits for input
  • End session: Prefix with END — displays a message and closes the session

Let’s build a practical example: a customer service menu for a mobile money application.

Code Example: Python (Flask)

This is how to create a USSD code application using Python and Flask. The example builds a QuickPay customer service menu with balance checks, money transfers, and airtime purchases.

from flask import Flask, request

app = Flask(__name__)
sessions = {}

@app.route('/ussd', methods=['POST'])
def ussd_callback():
    session_id = request.form.get('sessionId')
    phone_number = request.form.get('phoneNumber')
    text = request.form.get('text', '')

    inputs = text.split('*') if text else []
    level = len(inputs)

    if level == 0:
        response = "CON Welcome to QuickPay\n"
        response += "1. Check Balance\n"
        response += "2. Send Money\n"
        response += "3. Buy Airtime"
    elif inputs[0] == '1':
        response = "END Your balance is GHS 150.00"
    elif inputs[0] == '2' and level == 1:
        response = "CON Enter recipient number:"
    elif inputs[0] == '2' and level == 2:
        response = "CON Enter amount (GHS):"
    elif inputs[0] == '2' and level == 3:
        amount = inputs[2]
        recipient = inputs[1]
        response = f"END Sending GHS {amount} to {recipient}. You will receive an SMS confirmation."
    elif inputs[0] == '3' and level == 1:
        response = "CON Enter amount (GHS):"
    elif inputs[0] == '3' and level == 2:
        response = f"END Airtime of GHS {inputs[1]} purchased for {phone_number}"
    else:
        response = "END Invalid option. Please try again."

    return response

if __name__ == '__main__':
    app.run(port=5000, debug=True)

This example uses in-memory storage for simplicity. For production, replace with Redis (covered in the session management section below).

Code Example: Node.js (Express)

Here’s how to build a USSD application in Node.js — giving you the flexibility to develop in whichever language your team prefers.

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

const sessions = new Map();

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 Welcome to QuickPay\n';
        response += '1. Check Balance\n';
        response += '2. Send Money\n';
        response += '3. Buy Airtime';
    } else if (inputs[0] === '1') {
        response = 'END Your balance is GHS 150.00';
    } else if (inputs[0] === '2' && level === 1) {
        response = 'CON Enter recipient number:';
    } else if (inputs[0] === '2' && level === 2) {
        response = 'CON Enter amount (GHS):';
    } else if (inputs[0] === '2' && level === 3) {
        response = `END Sending GHS ${inputs[2]} to ${inputs[1]}. SMS confirmation will follow.`;
    } else if (inputs[0] === '3' && level === 1) {
        response = 'CON Enter amount (GHS):';
    } else if (inputs[0] === '3' && level === 2) {
        response = `END Airtime of GHS ${inputs[1]} purchased for ${phoneNumber}`;
    } else {
        response = 'END Invalid option. Please try again.';
    }

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

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

Both examples follow identical logic. Pick the language your team knows best — the USSD API is language-agnostic.

USSD Session Management Best Practices

USSD session state storage comparison infographic showing in-memory, Redis, and database strategies with latency, auto-expiry, and scalability trade-offs for USSD applications

Session handling separates solid USSD apps from frustrating ones. Every developer building for Ghanaian networks needs to get this right.

Handle Timeouts Gracefully

USSD sessions timeout after 120–180 seconds depending on the carrier. Your application must account for this:

  • Keep menu depths shallow — 3–4 levels maximum. Each level consumes session time.
  • Store partial session data server-side so users can resume if a session drops.
  • Send an SMS confirmation for completed transactions — users won’t always see the final USSD screen.

Choose the Right State Persistence Strategy (Redis vs Database)

You have three options for tracking session state:

StrategyBest ForTrade-off
In-memory (dictionary/Map)Prototyping, single-server setupsLost on restart, doesn’t scale
RedisProduction, multi-server deploymentsFast, auto-expiry with TTL, scales horizontally
DatabaseComplex flows needing audit trailsSlower reads, but persistent and queryable

For production USSD applications, Redis is the standard choice. Set your TTL to match the USSD session timeout (180 seconds) so stale sessions clean themselves up.

import redis

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# Store session state with auto-expiry
r.setex(f"ussd:{session_id}", 180, json.dumps(session_data))

# Retrieve session state
data = json.loads(r.get(f"ussd:{session_id}") or '{}')

Why Redis over a database for USSD? USSD sessions are short-lived (under 3 minutes) and high-throughput. Redis reads in under 1ms. A database round-trip takes 5–20ms — that latency compounds across a multi-step menu flow. Use a database alongside Redis when you need persistent audit trails for financial transactions or compliance.

Parse Input Correctly

The text field accumulates all user inputs separated by *. A user navigating Main Menu → Send Money → Enter Number produces: "2*0241234567".

Always split on * and use the array length to determine menu depth. Never rely on the raw text string directly — edge cases with asterisks in input will break your flow.

Handle Concurrent Sessions

When multiple users hit your shortcode simultaneously, race conditions surface if your code isn’t thread-safe. Use the sessionId as the unique key for all state operations. Avoid global mutable state. If you’re using Redis, each session ID naturally isolates data.

Test with concurrent load using tools like k6 or Apache Bench to verify your application handles parallel sessions without state leaks.

Securing Your USSD Application

USSD applications often handle sensitive data — financial transactions, personal details, authentication flows. Security cannot be an afterthought. For a comprehensive treatment, see our guide on USSD security best practices.

Validate and Sanitize All Input

Users can type anything into a USSD prompt — not just the numbers you expect. Validate every input against expected patterns before processing:

import re

def validate_phone(number):
    return bool(re.match(r'^0[235]\d{8}$', number))

def validate_amount(amount):
    try:
        val = float(amount)
        return 0 < val <= 10000
    except ValueError:
        return False

def sanitize_input(text):
    return text.strip()[:20]  # Limit length, strip whitespace

Never pass raw USSD input directly to database queries or shell commands. Treat every input as untrusted.

Protect Against Session Hijacking

  • Validate session origin — check that incoming requests come from Arkesel's known IP ranges. Whitelist these IPs in your firewall or application middleware.
  • Bind sessions to phone numbers — if a sessionId arrives with a different phoneNumber than the one that initiated it, reject the request.
  • Set strict TTLs — expire session data in Redis after the protocol timeout (180 seconds). Lingering sessions are attack surface.

Secure Sensitive Data in Transit

  • HTTPS only — your callback URL must use TLS. No exceptions.
  • Never log sensitive data — PINs, account numbers, and OTPs must not appear in application logs or error messages.
  • Rate limiting — cap the number of USSD sessions per phone number per minute to prevent brute-force attacks on PIN-protected flows.

Testing Your USSD Application

Thorough testing is critical before going live on Ghanaian networks. Once you know how to create a USSD code and build your USSD application, the next step is validating every menu path. A buggy USSD app doesn't just frustrate users — it erodes trust in your brand.

Local Development Testing

Start by testing your callback endpoint locally. Use curl or Postman to simulate USSD callback requests:

curl -X POST http://localhost:5000/ussd \
  -d "sessionId=test-001" \
  -d "serviceCode=*384*1234#" \
  -d "phoneNumber=+233241234567" \
  -d "text="

To expose your local server to Arkesel's callback during development, use a tunneling tool like ngrok:

ngrok http 5000

Copy the HTTPS URL and set it as your callback in the Arkesel dashboard. This lets you test the full callback flow without deploying to a server.

Staging Environment Testing

Arkesel's sandbox environment lets you test with simulated USSD sessions before connecting to live telco networks. This catches integration issues — callback URL accessibility, payload parsing, response formatting — without burning real shortcode sessions.

Automated Testing

Write unit tests for your menu logic. Each menu state is a pure function: given a text input, it returns a response string. This makes USSD apps highly testable.

def test_main_menu():
    response = ussd_callback(session_id='test', text='')
    assert response.startswith('CON')
    assert 'Check Balance' in response

def test_check_balance():
    response = ussd_callback(session_id='test', text='1')
    assert response.startswith('END')
    assert 'GHS' in response

def test_invalid_input():
    response = ussd_callback(session_id='test', text='9')
    assert response.startswith('END')
    assert 'Invalid' in response

For end-to-end testing of multi-step flows, build a USSD simulator that replays a sequence of inputs and validates each response. This catches state management bugs that unit tests miss.

Deploying on Ghana's Telco Networks

USSD application go-live checklist for Ghana with infrastructure requirements, carrier testing coverage for MTN Telecel and AirtelTigo, and production performance targets

Taking your USSD application from development to production on Ghanaian carriers requires understanding the regulatory and technical landscape.

Carrier-Specific Considerations

Ghana's three mobile networks each have nuances:

  • MTN Ghana — The largest subscriber base. Test here first. All USSD shortcodes require NCA registration for dedicated codes. Shared shortcodes through aggregators like Arkesel bypass individual carrier negotiations.
  • Telecel Ghana (formerly Vodafone Ghana) — Rebranded in 2025. Some legacy documentation still references "Vodafone" in carrier metadata. Ensure your logging and carrier-detection logic accounts for both names.
  • AirtelTigo — Smaller subscriber base, but essential for full market coverage. Session timeout behavior may differ slightly — test timeout handling specifically on this network.

Go Live Checklist

  • ☑ Callback URL is HTTPS, publicly accessible, and responds under 10 seconds
  • ☑ Response logic tested on all three carriers (MTN, Telecel, AirtelTigo)
  • ☑ Session state persisted in Redis (not in-memory)
  • ☑ Error handling returns user-friendly END messages (never raw stack traces)
  • ☑ Monitoring in place for session completion rates and response times
  • ☑ SMS fallback configured for interrupted critical transactions
  • Menu design tested for character limits (stay under 160 characters per screen)
  • ☑ Input validation and sanitization active on all user-facing prompts

Common Pitfalls and Debugging Tips

After working with hundreds of USSD deployments across Africa, these are the issues that trip up developers most often.

1. Response Too Long

USSD screens display a maximum of 182 characters (160 on some networks). Exceed this and your menu gets truncated — or worse, the session fails silently.

Fix: Keep each response under 160 characters. Use abbreviations and short labels. Test on actual handsets, not just simulators.

2. Callback URL Not Reachable

The most common integration failure. Your server must be publicly accessible, respond within 10 seconds, and return a 200 status code.

Fix: Verify your URL is HTTPS-enabled and accessible from outside your network. Use health check endpoints. Monitor response times.

3. Session State Lost Between Requests

If your server restarts or you're running multiple instances behind a load balancer, in-memory session stores vanish.

Fix: Use Redis or a database for session persistence. Never rely on server memory in production.

4. Character Encoding Issues

Special characters and non-ASCII text (common in local languages) can corrupt USSD responses.

Fix: Stick to GSM 7-bit character set. Avoid emojis, special symbols, and characters outside the standard GSM alphabet.

5. Not Handling Concurrent Sessions

Multiple users hitting your shortcode simultaneously creates race conditions if your code isn't thread-safe.

Fix: Use session ID as the unique key for all state operations. Avoid global variables. Test with concurrent load using tools like Apache Bench or k6.

6. Ignoring Network Variability

Different carriers handle USSD slightly differently. A flow that works on MTN Ghana might behave differently on Telecel.

Fix: Test across all target carriers before launch. Log the serviceCode and carrier metadata to identify carrier-specific issues.

7. Not Escaping Special Characters in USSD Codes

USSD codes contain * and #, which have special meaning in many programming contexts (regex, Markdown, shell). Improper escaping corrupts display or breaks parsing.

Fix: Wrap USSD codes in code blocks or escape * and # in your rendering layer. In documentation and user-facing output, validate that codes like *384*1234# display correctly.

Scaling Your USSD Application

Once your application handles real traffic, performance becomes critical. USSD users expect instant responses — any delay feels like the session is hanging.

  • Response time target: Under 2 seconds. Anything over 5 seconds risks session timeout on some networks.
  • Horizontal scaling: Run multiple server instances behind a load balancer. Use Redis for shared session state so any instance can handle any request.
  • Database optimization: Cache frequently accessed data (account balances, user profiles). Avoid database queries that take more than 500ms.
  • Monitoring: Track session completion rates, average response times, and error rates per carrier. Drop-offs at specific menu levels reveal UX problems.
  • Load testing: Simulate concurrent USSD sessions at peak volumes. Mobile money and airtime top-up services spike during salary dates and holidays — design your infrastructure for 3–5x your average load.

For high-throughput USSD services (mobile money, airtime top-up, payment processing), consider a dedicated load balancer layer with health checks that remove unresponsive instances from the pool automatically.

What to Build Next

You now know how to create a USSD code and have a working application with proper session management, tested and ready for deployment. Here's where to go from here:

Ready to build your USSD application? Create your Arkesel developer account, grab your API key, and follow the USSD API documentation to go live.

Frequently Asked Questions

How long does it take to set up a USSD application?

With Arkesel's USSD API, you can build a working application in a single day. The code itself takes hours. Getting a live shortcode connected to production telco networks typically takes 1–3 business days for shared codes, or 2–4 weeks for dedicated shortcodes that require NCA registration.

Can I build a USSD application without a shortcode?

You need a shortcode to reach users on live networks. However, you can build and test your entire application logic using Arkesel's sandbox environment before securing a shortcode. This lets you validate your menu flows, session management, and business logic before committing to a code.

What programming languages work with the USSD API?

Any language that handles HTTP POST requests works. When learning how to create a USSD code, the language choice rarely matters — the USSD API uses standard HTTP callbacks — your server receives a POST request and returns plain text. Python (Flask, Django), Node.js (Express), PHP, Java (Spring Boot), Ruby, and Go are all common choices among developers in Ghana.

How do I handle USSD sessions that timeout?

Store session state in Redis with a TTL matching the carrier timeout (typically 180 seconds). When a session expires, clean up any pending transactions. For financial applications, implement idempotency keys so retried transactions don't process twice. Send an SMS to the user if their session was interrupted during a critical flow.

What is the character limit for USSD messages?

The standard limit is 182 characters per screen, though some carriers cap it at 160 characters. Design your menus to stay under 160 characters for maximum carrier compatibility. Use numbered options (1, 2, 3) instead of lettered ones to save characters.

How much does a USSD shortcode cost in Ghana?

Pricing depends on whether you choose a shared or dedicated shortcode, your expected session volume, and your provider. Shared shortcodes cost less and launch faster. Dedicated codes carry a premium and require NCA registration. Check Arkesel's current pricing for the latest rates.

What is the difference between shared and dedicated USSD shortcodes?

A shared shortcode (e.g., *384*100#) is one code used by multiple businesses, each with a unique extension. It's faster to set up and more cost-effective. A dedicated shortcode (e.g., *920#) belongs exclusively to your business — stronger brand recognition, but requires NCA registration and a longer setup timeline.

Related Articles

Popular Posts

Choose the right USSD shortcode provider in Ghana. This 8-point checklist covers network coverage, NCA compliance, API quality, uptime, and total cost.

Unstructured Supplementary Service Data (USSD) technology has become an essential mobile communication tool. Notably, it is a straightforward yet effective communication system. The simplicity, accessibility, and reliability of USSD make it a compelling platform for

As mobile technology evolves, Unstructured Supplementary Service Data (USSD) is a powerful tool for developers seeking innovative applications and services. While it may seem overshadowed by more modern technologies like SMS and mobile apps, USSD

Scroll to Top