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