API Reference
Complete API specification for the OLA USSD Gateway.
Base URL
http://localhost:8080 # Development
https://gateway.example.com # Production
Authentication
Currently, the gateway API does not require authentication for backend callbacks. Authentication may be added in future versions.
For production deployments, secure your gateway with:
- API keys
- IP whitelisting
- VPN/private network
- mTLS (mutual TLS)
Content Type
All requests and responses use application/json.
Content-Type: application/json
Gateway Management APIs
These APIs are used to manage the gateway itself.
Health Check
Check if the gateway is running.
Endpoint: GET /health
Request:
GET /health HTTP/1.1
Host: localhost:8080
Response:
{
"status": "ok",
"service": "ola-ussd-gateway",
"version": "1.0.0",
"uptime": 3600
}
Status Codes:
200 OK- Gateway is healthy503 Service Unavailable- Gateway is unhealthy
Backend Management APIs
These APIs are used to register and manage your backend applications.
Register Backend
Register a new backend application with the gateway.
Endpoint: POST /backends
Request:
POST /backends HTTP/1.1
Host: localhost:8080
Content-Type: application/json
{
"callback_url": "http://your-backend.com:8081/ussd/callback",
"service_code": "*365#",
"name": "Banking Service",
"type": "http",
"timeout": 5,
"retries": 2,
"method": "POST",
"status": "active"
}
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
callback_url | string | Yes | URL to your backend's webhook endpoint |
service_code | string | Yes | USSD short code (e.g., *365#) |
name | string | Yes | Human-readable backend name |
type | string | Yes | Backend type (http only for now) |
timeout | integer | No | Request timeout in seconds (default: 5) |
retries | integer | No | Number of retry attempts (default: 2) |
method | string | Yes | HTTP method (POST, GET, PUT) |
status | string | Yes | active or inactive |
Response:
{
"id": 1,
"callback_url": "http://your-backend.com:8081/ussd/callback",
"service_code": "*365#",
"name": "Banking Service",
"type": "http",
"timeout": 5,
"retries": 2,
"method": "POST",
"status": "active",
"created_at": "2025-01-20T10:30:00Z",
"updated_at": "2025-01-20T10:30:00Z"
}
Status Codes:
201 Created- Backend registered successfully400 Bad Request- Invalid request body409 Conflict- Service code already registered
List All Backends
Get all registered backends.
Endpoint: GET /backends
Request:
GET /backends HTTP/1.1
Host: localhost:8080
Response:
[
{
"id": 1,
"callback_url": "http://backend1.com/ussd",
"service_code": "*365#",
"name": "Banking Service",
"type": "http",
"timeout": 5,
"retries": 2,
"method": "POST",
"status": "active",
"created_at": "2025-01-20T10:30:00Z",
"updated_at": "2025-01-20T10:30:00Z"
},
{
"id": 2,
"callback_url": "http://backend2.com/ussd",
"service_code": "*105#",
"name": "Utility Payments",
"type": "http",
"timeout": 5,
"retries": 2,
"method": "POST",
"status": "inactive",
"created_at": "2025-01-20T11:00:00Z",
"updated_at": "2025-01-20T11:00:00Z"
}
]
Status Codes:
200 OK- Success
Get Backend by ID
Get a specific backend by its ID.
Endpoint: GET /backends/{id}
Request:
GET /backends/1 HTTP/1.1
Host: localhost:8080
Response:
{
"id": 1,
"callback_url": "http://backend1.com/ussd",
"service_code": "*365#",
"name": "Banking Service",
"type": "http",
"timeout": 5,
"retries": 2,
"method": "POST",
"status": "active",
"created_at": "2025-01-20T10:30:00Z",
"updated_at": "2025-01-20T10:30:00Z"
}
Status Codes:
200 OK- Backend found404 Not Found- Backend not found
Get Backend by Service Code
Get a backend by its service code.
Endpoint: GET /backends/service?code={service_code}
Request:
GET /backends/service?code=*365%23 HTTP/1.1
Host: localhost:8080
Note: URL-encode the
#symbol as%23
Response:
{
"id": 1,
"callback_url": "http://backend1.com/ussd",
"service_code": "*365#",
"name": "Banking Service",
"type": "http",
"timeout": 5,
"retries": 2,
"method": "POST",
"status": "active",
"created_at": "2025-01-20T10:30:00Z",
"updated_at": "2025-01-20T10:30:00Z"
}
Status Codes:
200 OK- Backend found404 Not Found- No backend registered for this service code
Update Backend
Update an existing backend.
Endpoint: PUT /backends
Request:
PUT /backends HTTP/1.1
Host: localhost:8080
Content-Type: application/json
{
"id": 1,
"callback_url": "http://new-backend.com/ussd",
"service_code": "*365#",
"name": "Banking Service v2",
"type": "http",
"timeout": 10,
"retries": 3,
"method": "POST",
"status": "active"
}
Request Body: Same as register, plus id field
Response:
{
"id": 1,
"callback_url": "http://new-backend.com/ussd",
"service_code": "*365#",
"name": "Banking Service v2",
"type": "http",
"timeout": 10,
"retries": 3,
"method": "POST",
"status": "active",
"created_at": "2025-01-20T10:30:00Z",
"updated_at": "2025-01-20T12:00:00Z"
}
Status Codes:
200 OK- Backend updated400 Bad Request- Invalid request404 Not Found- Backend not found
Delete Backend
Delete a backend registration.
Endpoint: DELETE /backends?id={id}
Request:
DELETE /backends?id=1 HTTP/1.1
Host: localhost:8080
Response:
{
"message": "Backend deleted successfully",
"id": 1
}
Status Codes:
200 OK- Backend deleted404 Not Found- Backend not found
USSD Callback API
This is the API contract between the gateway and your backend application.
USSD Callback
The gateway calls this endpoint on your backend for each USSD request.
Endpoint: POST {your_callback_url}
Example: POST http://your-backend.com:8081/ussd/callback
Request from Gateway:
POST /ussd/callback HTTP/1.1
Host: your-backend.com:8081
Content-Type: application/json
{
"provider": "movitel",
"msisdn": "258823456789",
"session_id": "sess_abc123",
"transaction_id": "txn_xyz789",
"input": "1"
}
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | Provider name: movitel, vodacom, tmcel , or test |
msisdn | string | Yes | User's phone number (E.164 without +) |
session_id | string | Yes | Session identifier (persistent across requests) |
transaction_id | string | Yes | Unique transaction ID for this request |
input | string | Yes | User's input (empty on first dial) |
Response from Your Backend:
HTTP/1.1 200 OK
Content-Type: application/json
{
"session_id": "sess_abc123",
"transaction_id": "txn_xyz789",
"output": [
"Welcome to MyBank USSD",
"",
"1. Check Balance",
"2. Transfer Money",
"0. Exit"
],
"end_session": false
}
Response Body:
| Field | Type | Required | Description |
|---|---|---|---|
session_id | string | Yes | Must match request's session_id |
transaction_id | string | Yes | Must match request's transaction_id |
output | string[] | Yes | Array of strings to display |
end_session | boolean | Yes | true to end session, false to continue |
Status Codes:
200 OK- Request processed successfully400 Bad Request- Invalid request format500 Internal Server Error- Backend error (gateway will retry)503 Service Unavailable- Backend temporarily unavailable
Request/Response Examples
Example 1: Initial Dial
User dials *365# for the first time.
Gateway → Your Backend:
{
"provider": "movitel",
"msisdn": "258823456789",
"session_id": "sess_001",
"transaction_id": "txn_001",
"input": ""
}
Your Backend → Gateway:
{
"session_id": "sess_001",
"transaction_id": "txn_001",
"output": [
"Welcome to MyBank",
"",
"1. Check Balance",
"2. Transfer",
"0. Exit"
],
"end_session": false
}
Example 2: Menu Selection
User presses 1 to check balance.
Gateway → Your Backend:
{
"provider": "movitel",
"msisdn": "258823456789",
"session_id": "sess_001",
"transaction_id": "txn_002",
"input": "1"
}
Your Backend → Gateway:
{
"session_id": "sess_001",
"transaction_id": "txn_002",
"output": [
"Account Balance",
"",
"Available: 1,234.56 MZN",
"Reserved: 100.00 MZN"
],
"end_session": true
}
Example 3: Multi-Step Flow
User enters an amount.
Gateway → Your Backend:
{
"msisdn": "258843456789",
"session_id": "sess_002",
"transaction_id": "txn_101",
"input": "5000"
}
Your Backend → Gateway:
{
"session_id": "sess_002",
"transaction_id": "txn_101",
"output": [
"Transfer Confirmation",
"",
"Amount: 5000 MZN",
"To: 258821234567",
"",
"1. Confirm",
"0. Cancel"
],
"end_session": false
}
Example 4: Error Response
User enters invalid option.
Gateway → Your Backend:
{
"msisdn": "258863456789",
"session_id": "sess_003",
"transaction_id": "txn_201",
"input": "9"
}
Your Backend → Gateway:
{
"session_id": "sess_003",
"transaction_id": "txn_201",
"output": [
"Invalid option",
"",
"Please select 1-3 or 0"
],
"end_session": false
}
Example 5: Backend Error
Backend processing fails.
Your Backend → Gateway:
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"session_id": "sess_004",
"transaction_id": "txn_301",
"output": [
"Service temporarily unavailable",
"",
"Please try again later"
],
"end_session": true
}
Testing Endpoints
Test USSD Flow
Simulate a USSD session for testing (not available in production).
Endpoint: POST /ussd/test/callback
Request:
POST /ussd/test/callback HTTP/1.1
Host: localhost:8080
Content-Type: application/json
{
"provider": "movitel",
"msisdn": "258823456789",
"session_id": "test_sess_001",
"transaction_id": "test_txn_001",
"service_code": "*365#",
"input": ""
}
Response: Same as USSD callback response
Error Responses
Gateway Errors
When the gateway itself encounters an error:
{
"error": "backend_not_found",
"message": "No backend registered for service code *365#",
"service_code": "*365#"
}
Common Error Codes:
| Code | Description |
|---|---|
backend_not_found | No backend registered for service code |
backend_unavailable | Backend not responding |
backend_timeout | Backend request timed out |
invalid_request | Malformed request |
internal_error | Gateway internal error |
Backend Error Handling
If your backend returns an error or is unavailable, the gateway will:
- Retry the request (based on
retriesconfiguration) - Use exponential backoff between retries
- Eventually return an error to the user
Example error flow:
1. Gateway calls backend → Timeout
2. Wait 1 second
3. Gateway retries → Timeout
4. Wait 2 seconds
5. Gateway retries → Timeout
6. Return error to user
Rate Limiting
The gateway may implement rate limiting in future versions. Current implementation does not enforce rate limits.
Recommended limits:
- 100 requests per second per backend
- 1000 requests per minute per backend
Webhooks and Callbacks
Gateway → Backend Flow
Timeout Handling
- Gateway waits up to
timeoutseconds for your response - If timeout occurs, gateway retries
- After all retries fail, gateway returns error to user
Retry Strategy
Attempt 1: Immediate
Attempt 2: After 1 second
Attempt 3: After 2 seconds
Attempt 4: After 4 seconds (if retries = 3)
Best Practices
1. Implement Health Check
Your backend should have a health check endpoint:
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
2. Log All Requests
Log every request for debugging:
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
msisdn: req.body.msisdn,
session_id: req.body.session_id,
transaction_id: req.body.transaction_id,
input: req.body.input
}));
3. Handle Timeouts
Implement timeout handling in your backend:
app.use((req, res, next) => {
req.setTimeout(5000); // 5 second timeout
next();
});
4. Validate Requests
Validate all incoming requests:
function validateRequest(req, res, next) {
const required = ['msisdn', 'session_id', 'transaction_id'];
for (const field of required) {
if (!req.body[field]) {
return res.status(400).json({
error: 'missing_field',
message: `${field} is required`
});
}
}
next();
}
app.post('/ussd/callback', validateRequest, handleUSSD);
5. Implement Idempotency
Use transaction_id to prevent duplicate processing:
const processedTransactions = new Set();
app.post('/ussd/callback', (req, res) => {
const { transaction_id } = req.body;
if (processedTransactions.has(transaction_id)) {
console.warn(`Duplicate transaction: ${transaction_id}`);
// Return cached response
return res.json(getCachedResponse(transaction_id));
}
processedTransactions.add(transaction_id);
// Process request...
});
SDK and Libraries
Official SDKs
Currently, no official SDKs are available. The API is simple HTTP/JSON and can be used with any HTTP client.
Community Libraries
Community-contributed libraries may be available. Check:
- GitHub: https://github.com/pavulla-tech/ola-ussd-gateway
- NPM: Search for "ola-ussd"
DIY Integration
You can easily integrate using standard HTTP libraries:
Node.js:
const axios = require('axios');
// Register backend
await axios.post('https://ussd.ola.pavulla.com/backends', {
callback_url: '"https://{your-domain-or-public-ip}/ussd/callback',
service_code: '*365#',
name: 'MyBackend',
type: 'http',
method: 'POST',
status: 'active'
});
Python:
import requests
# Register backend
response = requests.post('https://ussd.ola.pavulla.com/backends', json={
'callback_url': '"https://{your-domain-or-public-ip}/ussd/callback',
'service_code': '*365#',
'name': 'MyBackend',
'type': 'http',
'method': 'POST',
'status': 'active'
})
Versioning
The API currently uses implicit versioning through the base URL. Future versions may include explicit versioning:
/v1/backends
/v2/backends
Breaking changes will be announced with migration guides.
Support
- 💬 Examples
- 🐛 Report Issues
- 📧 Contact