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?​