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:
- Receives the provider-specific request (SOAP/XML/JSON)
- Normalizes it to a standard JSON format
- POSTs to your backend's callback URL
- Receives your JSON response
- 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
| Field | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | Provider name: movitel, vodacom, tmcel , or test |
msisdn | string | Yes | User's phone number (E.164 format without +) |
session_id | string | Yes | Session identifier (persistent across interactions) |
transaction_id | string | Yes | Unique transaction ID for this specific request |
input | string | Yes | User'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
| Field | Type | Required | Description |
|---|---|---|---|
session_id | string | Yes | Must match the request's session_id |
transaction_id | string | Yes | Must match the request's transaction_id |
output | string[] | Yes | Array of strings to display (menu text) |
end_session | boolean | Yes | true 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 Code | Usage | Description |
|---|---|---|
200 OK | Success | Request processed successfully |
400 Bad Request | Client error | Invalid request format |
500 Internal Server Error | Server error | Backend processing failed |
503 Service Unavailable | Temporary error | Backend temporarily unavailable |
Gateway Behavior:
200- Gateway forwards your response to provider400- Gateway logs error, may retry500- Gateway retries based on configuration503- 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 Unauthorizedif header is missing - Return
404 Not Foundif organization doesn't exist - Return
403 Forbiddenif 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 Code | Error | Description |
|---|---|---|
| 400 | invalid_request | Missing or invalid request parameters |
| 401 | unauthorized | Missing or invalid X-Organization-ID header |
| 403 | forbidden | Organization is not active |
| 404 | not_found | Organization or backend not found |
| 409 | conflict | Backend with service code already exists |
| 500 | internal_error | Server 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
- Backend Registration - Register your backend with the gateway
- Session Management - Handle user sessions
- Menu Flows - Build menu navigation
- API Reference - Complete API docs