Skip to main content

API Contract

Understanding the request and response formats between the OLA Gateway and your backend application.

Overview

The OLA Gateway communicates with your backend using a simple JSON-based HTTP API. When a user interacts with USSD, the gateway:

  1. Receives the provider-specific request (SOAP/XML/JSON)
  2. Normalizes it to a standard JSON format
  3. POSTs to your backend's callback URL
  4. Receives your JSON response
  5. Converts it back to the provider's format

All communication is in JSON - you never deal with provider protocols directly.

Request Format

Gateway → Your Backend

When the gateway forwards a request to your backend, it sends:

POST /ussd/callback HTTP/1.1
Host: your-backend.com
Content-Type: application/json

{
"provider": "movitel",
"msisdn": "258823456789",
"session_id": "sess_abc123",
"transaction_id": "txn_xyz789",
"input": "1"
}

Request Fields

FieldTypeRequiredDescription
providerstringYesProvider name: movitel, vodacom, tmcel , or test
msisdnstringYesUser's phone number (E.164 format without +)
session_idstringYesSession identifier (persistent across interactions)
transaction_idstringYesUnique transaction ID for this specific request
inputstringYesUser's input (empty string on first dial)

msisdn

The user's phone number in international format without the + symbol.

Format: Country code + area code + number

  • Example: 258823456789 (Mozambique)
  • Pattern: ^258[0-9]{9}$ (Mozambique numbers)

Use cases:

  • Identify users
  • Validate access permissions
  • Send SMS confirmations
  • Store user preferences
const userId = await getUserByMSISDN(req.body.msisdn);

session_id

A unique identifier for the USSD session, persistent across multiple requests within the same session.

Characteristics:

  • Same value for all requests in one session
  • Changes when user starts a new session
  • Format: sess_ + random string
  • Typically 20-50 characters

Use case: Track the entire user journey.

// Same session_id across multiple requests
// Request 1: session_id = "sess_abc123"
// Request 2: session_id = "sess_abc123" (same)
// Request 3: session_id = "sess_abc123" (same)

transaction_id

A unique identifier for each individual request within a session.

Characteristics:

  • Unique for every request
  • Different even within the same session
  • Format: txn_ + random string
  • Used for idempotency and tracking

Use case: Prevent duplicate processing, audit logging.

// Different transaction_id for each request
// Request 1: transaction_id = "txn_001"
// Request 2: transaction_id = "txn_002"
// Request 3: transaction_id = "txn_003"

input

The user's input from their keypad.

Possible values:

  • "" (empty string) - First dial, no input yet
  • "1", "2", etc. - User selected option
  • "0" - Usually "back" or "exit"
  • "123456" - User-entered data (PIN, amount, etc.)
  • "*" or "#" - Special keys (rare, provider-dependent)

Use case: Route to appropriate handler based on input.

if (input === "") {
return showMainMenu();
} else if (input === "1") {
return checkBalance();
}

Response Format

Your Backend → Gateway

Your backend must respond with this exact JSON structure:

HTTP/1.1 200 OK
Content-Type: application/json

{
"session_id": "sess_abc123",
"transaction_id": "txn_xyz789",
"output": [
"Welcome to MyBank",
"",
"1. Check Balance",
"2. Transfer Money",
"3. Buy Airtime",
"0. Exit"
],
"end_session": false
}

Response Fields

FieldTypeRequiredDescription
session_idstringYesMust match the request's session_id
transaction_idstringYesMust match the request's transaction_id
outputstring[]YesArray of strings to display (menu text)
end_sessionbooleanYestrue to end session, false to continue

Field Details

session_id and transaction_id

Must echo back the exact values from the request.

// ✅ Correct
res.json({
session_id: req.body.session_id,
transaction_id: req.body.transaction_id,
// ...
});

// ❌ Wrong - don't modify these
res.json({
session_id: "my-custom-id", // DON'T DO THIS
// ...
});

output

An array of strings representing the menu or message to display.

Rules:

  • Must be an array (even for single line)
  • Each element is a separate line
  • Empty strings create blank lines
  • Maximum ~180 characters total (provider-dependent)
  • Use short, clear text

Examples:

Simple menu:

{
"output": [
"Main Menu",
"1. Option A",
"2. Option B",
"0. Exit"
]
}

Single message:

{
"output": [
"Your balance is 500.00 MZN"
]
}

Multi-line message with spacing:

{
"output": [
"Transaction Complete",
"",
"Amount: 100 MZN",
"Ref: TXN123456",
"",
"Thank you!"
]
}

Character Limits: Different providers have different limits:

  • Movitel: ~160 characters
  • Vodacom: ~182 characters
  • Tmcel: ~160 characters

Keep total output under 160 characters to be safe.

end_session

Controls whether the session continues or ends.

false - Continue Session:

  • User can enter more input
  • Gateway will send another request
  • Use for multi-step flows
{
"output": ["Enter amount:", ""],
"end_session": false // Wait for user input
}

true - End Session:

  • Session terminates
  • No more input accepted
  • Use for final messages
{
"output": ["Thank you!", "Transaction complete"],
"end_session": true // Session ends
}

Request/Response Examples

Example 1: Initial Dial

User Action: Dials *365#

Request:

{
"provider": "movitel",
"msisdn": "258823456789",
"session_id": "sess_1a2b3c",
"transaction_id": "txn_001",
"input": ""
}

Response:

{
"session_id": "sess_1a2b3c",
"transaction_id": "txn_001",
"output": [
"Welcome to MyBank USSD",
"",
"1. Check Balance",
"2. Transfer Money",
"3. Buy Airtime",
"0. Exit"
],
"end_session": false
}

Example 2: User Selection

User Action: Presses 1

Request:

{
"provider": "movitel",
"msisdn": "258823456789",
"session_id": "sess_1a2b3c",
"transaction_id": "txn_002",
"input": "1"
}

Response:

{
"session_id": "sess_1a2b3c",
"transaction_id": "txn_002",
"output": [
"Your Balance",
"",
"Available: 1,234.56 MZN",
"Reserved: 100.00 MZN",
"",
"Press 0 to exit"
],
"end_session": false
}

Example 3: Session End

User Action: Presses 0

Request:

{
"provider": "movitel",
"msisdn": "258823456789",
"session_id": "sess_1a2b3c",
"transaction_id": "txn_003",
"input": "0"
}

Response:

{
"session_id": "sess_1a2b3c",
"transaction_id": "txn_003",
"output": [
"Thank you for using MyBank!",
"",
"Dial *365# anytime"
],
"end_session": true
}

Example 4: Multi-Step Input

User Action: Entering amount (e.g., "5000")

Request:

{
"msisdn": "258843456789",
"session_id": "sess_xyz789",
"transaction_id": "txn_101",
"input": "5000"
}

Response:

{
"session_id": "sess_xyz789",
"transaction_id": "txn_101",
"output": [
"Transfer Confirmation",
"",
"Amount: 5000 MZN",
"To: 258821234567",
"",
"1. Confirm",
"0. Cancel"
],
"end_session": false
}

Example 5: Error Response

User Action: Invalid input

Request:

{
"msisdn": "258863456789",
"session_id": "sess_err123",
"transaction_id": "txn_201",
"input": "9"
}

Response:

{
"session_id": "sess_err123",
"transaction_id": "txn_201",
"output": [
"Invalid option",
"",
"Please try again",
"",
"1. Option A",
"2. Option B",
"0. Exit"
],
"end_session": false
}

HTTP Status Codes

Your backend should use appropriate HTTP status codes:

Status CodeUsageDescription
200 OKSuccessRequest processed successfully
400 Bad RequestClient errorInvalid request format
500 Internal Server ErrorServer errorBackend processing failed
503 Service UnavailableTemporary errorBackend temporarily unavailable

Gateway Behavior:

  • 200 - Gateway forwards your response to provider
  • 400 - Gateway logs error, may retry
  • 500 - Gateway retries based on configuration
  • 503 - Gateway retries with backoff

Error Handling

Backend Errors

If your backend returns an error or is unavailable:

{
"session_id": "sess_abc123",
"transaction_id": "txn_xyz789",
"output": [
"Service temporarily unavailable",
"",
"Please try again later"
],
"end_session": true
}

Validation Errors

If request validation fails, return 400:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
"error": "missing_field",
"message": "msisdn is required"
}

Implementation Examples

Node.js (Express)

const express = require('express');
const app = express();
app.use(express.json());

app.post('/ussd/callback', (req, res) => {
// Validate request
const { msisdn, session_id, transaction_id, input } = req.body;

if (!msisdn || !session_id || !transaction_id || input === undefined) {
return res.status(400).json({
error: 'invalid_request',
message: 'Missing required fields'
});
}

try {
// Process request
const menu = processUSSDRequest(input, session_id, msisdn);

// Return response
res.json({
session_id,
transaction_id,
output: menu.lines,
end_session: menu.end
});
} catch (error) {
console.error('Error processing USSD:', error);
res.status(500).json({
session_id,
transaction_id,
output: ['Service error', '', 'Please try again'],
end_session: true
});
}
});

Python (Flask)

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/ussd/callback', methods=['POST'])
def ussd_callback():
# Validate request
data = request.json
required = ['msisdn', 'session_id', 'transaction_id', 'input']

if not all(field in data for field in required):
return jsonify({
'error': 'invalid_request',
'message': 'Missing required fields'
}), 400

try:
# Process request
menu = process_ussd_request(
data['input'],
data['session_id'],
data['msisdn']
)

# Return response
return jsonify({
'session_id': data['session_id'],
'transaction_id': data['transaction_id'],
'output': menu['lines'],
'end_session': menu['end']
})
except Exception as e:
print(f'Error processing USSD: {e}')
return jsonify({
'session_id': data['session_id'],
'transaction_id': data['transaction_id'],
'output': ['Service error', '', 'Please try again'],
'end_session': True
}), 500

Best Practices

1. Always Echo IDs

// ✅ Good
res.json({
session_id: req.body.session_id,
transaction_id: req.body.transaction_id,
// ...
});

2. Validate All Inputs

if (!input.match(/^[0-9*#]*$/)) {
return showError("Invalid input");
}

3. Keep Output Concise

// ✅ Good - Clear and concise
["Balance: 500 MZN"]

// ❌ Bad - Too verbose
["Your current available account balance as of today is 500 MZN"]

4. Handle Errors Gracefully

try {
const balance = await getBalance(msisdn);
return showBalance(balance);
} catch (error) {
return {
output: ["Service temporarily unavailable"],
end_session: true
};
}

5. Use Proper HTTP Status Codes

// Validation error
if (!isValidMSISDN(msisdn)) {
return res.status(400).json({ error: 'invalid_msisdn' });
}

// Processing error
catch (error) {
return res.status(500).json({ error: 'internal_error' });
}

Testing the Contract

Use curl to test your backend:

curl -X POST "https://{your-domain-or-public-ip}/ussd/callback \
-H "Content-Type: application/json" \
-d '{
"provider": "movitel",
"msisdn": "258823456789",
"session_id": "test_sess_123",
"transaction_id": "test_txn_001",
"input": ""
}'

Expected response:

{
"session_id": "test_sess_123",
"transaction_id": "test_txn_001",
"output": ["Your menu here"],
"end_session": false
}

Management API Endpoints

The OLA Gateway provides REST API endpoints for managing organizations and backends. These endpoints are used by administrators and developers to configure the gateway.

Authentication

Most management endpoints require organization authentication via the X-Organization-ID header:

X-Organization-ID: your-organization-uuid

Endpoints protected by this header will:

  • Return 401 Unauthorized if header is missing
  • Return 404 Not Found if organization doesn't exist
  • Return 403 Forbidden if organization is not active

Organization Management

Register Organization

Create a new organization in the gateway.

POST /organizations
Content-Type: application/json

{
"name": "MyBank",
"email": "admin@mybank.com",
"phone": "258823456789"
}

Response (201 Created):

{
"uuid": "org_abc123def456",
"name": "MyBank",
"email": "admin@mybank.com",
"phone": "258823456789",
"status": "active",
"balance": 0,
"created_at": "2025-10-21T10:30:00Z"
}

Get Organization

Retrieve organization details by UUID.

GET /organizations/{uuid}

Response (200 OK):

{
"uuid": "org_abc123def456",
"name": "MyBank",
"email": "admin@mybank.com",
"phone": "258823456789",
"status": "active",
"balance": 15000,
"created_at": "2025-10-21T10:30:00Z",
"updated_at": "2025-10-21T14:20:00Z"
}

Get Organization Balance

Retrieve current balance for an organization.

GET /organizations/{uuid}/balance

Response (200 OK):

{
"uuid": "org_abc123def456",
"balance": 15000,
"currency": "MZN"
}

Backend Management

All backend management endpoints require the X-Organization-ID header.

Register Backend

Register a new USSD backend service for your organization.

POST /backends
X-Organization-ID: org_abc123def456
Content-Type: application/json

{
"service_code": "*365#",
"callback_url": "https://mybank.com/ussd/callback",
"provider": "movitel",
"description": "MyBank USSD Service"
}

Response (201 Created):

{
"uuid": "backend_xyz789",
"organization_uuid": "org_abc123def456",
"service_code": "*365#",
"callback_url": "https://mybank.com/ussd/callback",
"provider": "movitel",
"description": "MyBank USSD Service",
"status": "active",
"created_at": "2025-10-21T10:35:00Z"
}

Get All Backends

Retrieve all backends for your organization.

GET /backends
X-Organization-ID: org_abc123def456

Response (200 OK):

{
"backends": [
{
"uuid": "backend_xyz789",
"service_code": "*365#",
"callback_url": "https://mybank.com/ussd/callback",
"provider": "movitel",
"description": "MyBank USSD Service",
"status": "active",
"created_at": "2025-10-21T10:35:00Z"
},
{
"uuid": "backend_abc456",
"service_code": "*365#",
"callback_url": "https://mybank.com/ussd/callback",
"provider": "vodacom",
"description": "MyBank Vodacom Service",
"status": "active",
"created_at": "2025-10-21T11:20:00Z"
}
],
"total": 2
}

Get Backend by Service Code

Retrieve a specific backend by service code.

GET /backends/service?service_code=*365#&provider=movitel
X-Organization-ID: org_abc123def456

Query Parameters:

  • service_code (required): The USSD service code (e.g., *365#)
  • provider (optional): Filter by provider (movitel, vodacom, tmcel)

Response (200 OK):

{
"uuid": "backend_xyz789",
"organization_uuid": "org_abc123def456",
"service_code": "*365#",
"callback_url": "https://mybank.com/ussd/callback",
"provider": "movitel",
"description": "MyBank USSD Service",
"status": "active",
"created_at": "2025-10-21T10:35:00Z"
}

Update Backend

Update an existing backend configuration.

PUT /backends
X-Organization-ID: org_abc123def456
Content-Type: application/json

{
"uuid": "backend_xyz789",
"callback_url": "https://mybank.com/ussd/v2/callback",
"description": "MyBank USSD Service v2",
"status": "active"
}

Response (200 OK):

{
"uuid": "backend_xyz789",
"organization_uuid": "org_abc123def456",
"service_code": "*365#",
"callback_url": "https://mybank.com/ussd/v2/callback",
"provider": "movitel",
"description": "MyBank USSD Service v2",
"status": "active",
"updated_at": "2025-10-21T15:45:00Z"
}

Delete Backend

Remove a backend from your organization.

DELETE /backends?uuid=backend_xyz789
X-Organization-ID: org_abc123def456

Query Parameters:

  • uuid (required): The backend UUID to delete

Response (200 OK):

{
"message": "Backend deleted successfully",
"uuid": "backend_xyz789"
}

Reports and Monitoring

The gateway provides HTML-based reporting endpoints for monitoring and analytics.

Dashboard

View organization dashboard with statistics and metrics.

GET /reports/dashboard/{uuid}

Returns an HTML page with:

  • Total sessions
  • Active sessions
  • Success/failure rates
  • Provider distribution
  • Recent activity

Sessions List

View all USSD sessions for an organization.

GET /reports/sessions/{uuid}

Returns an HTML page with a filterable list of sessions including:

  • Session ID
  • MSISDN
  • Provider
  • Start/end time
  • Status
  • Request count

Session Detail

View detailed information about a specific session.

GET /reports/session/{id}

Returns an HTML page with:

  • Full session timeline
  • All requests and responses
  • User inputs
  • Backend responses
  • Timing information
  • Error details (if any)

Error Responses

All endpoints follow consistent error response format:

{
"error": "error_code",
"message": "Human-readable error message"
}

Common Error Codes:

Status CodeErrorDescription
400invalid_requestMissing or invalid request parameters
401unauthorizedMissing or invalid X-Organization-ID header
403forbiddenOrganization is not active
404not_foundOrganization or backend not found
409conflictBackend with service code already exists
500internal_errorServer error during processing

Management API Examples

Complete Organization Setup

# 1. Register organization
curl -X POST https://gateway.ola.com/organizations \
-H "Content-Type: application/json" \
-d '{
"name": "MyBank",
"email": "admin@mybank.com",
"phone": "258823456789"
}'

# Response: {"uuid": "org_abc123", ...}

# 2. Register backend
curl -X POST https://gateway.ola.com/backends \
-H "Content-Type: application/json" \
-H "X-Organization-ID: org_abc123" \
-d '{
"service_code": "*365#",
"callback_url": "https://mybank.com/ussd/callback",
"provider": "movitel",
"description": "MyBank USSD"
}'

# 3. Check organization balance
curl -X GET https://gateway.ola.com/organizations/org_abc123/balance

# 4. List all backends
curl -X GET https://gateway.ola.com/backends \
-H "X-Organization-ID: org_abc123"

Update Backend URL

curl -X PUT https://gateway.ola.com/backends \
-H "Content-Type: application/json" \
-H "X-Organization-ID: org_abc123" \
-d '{
"uuid": "backend_xyz789",
"callback_url": "https://mybank.com/ussd/v2/callback"
}'

Monitor Sessions

# View dashboard in browser
open https://gateway.ola.com/reports/dashboard/org_abc123

# View sessions list
open https://gateway.ola.com/reports/sessions/org_abc123

# View specific session
open https://gateway.ola.com/reports/session/sess_abc123

Next Steps

Need Help?