Skip to main content

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 healthy
  • 503 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:

FieldTypeRequiredDescription
callback_urlstringYesURL to your backend's webhook endpoint
service_codestringYesUSSD short code (e.g., *365#)
namestringYesHuman-readable backend name
typestringYesBackend type (http only for now)
timeoutintegerNoRequest timeout in seconds (default: 5)
retriesintegerNoNumber of retry attempts (default: 2)
methodstringYesHTTP method (POST, GET, PUT)
statusstringYesactive 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 successfully
  • 400 Bad Request - Invalid request body
  • 409 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 found
  • 404 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 found
  • 404 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 updated
  • 400 Bad Request - Invalid request
  • 404 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 deleted
  • 404 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:

FieldTypeRequiredDescription
providerstringYesProvider name: movitel, vodacom, tmcel , or test
msisdnstringYesUser's phone number (E.164 without +)
session_idstringYesSession identifier (persistent across requests)
transaction_idstringYesUnique transaction ID for this request
inputstringYesUser'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:

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

Status Codes:

  • 200 OK - Request processed successfully
  • 400 Bad Request - Invalid request format
  • 500 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:

CodeDescription
backend_not_foundNo backend registered for service code
backend_unavailableBackend not responding
backend_timeoutBackend request timed out
invalid_requestMalformed request
internal_errorGateway internal error

Backend Error Handling​

If your backend returns an error or is unavailable, the gateway will:

  1. Retry the request (based on retries configuration)
  2. Use exponential backoff between retries
  3. 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 timeout seconds 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:

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​