This document describes the HTTP API exposed by the momopay.swazigist.co.za proxy. It explains how third-party apps can initiate mobile-money (MoMo) payments via MTN Collections, poll status, and receive payment result callbacks forwarded by the proxy.
Base URL
PROXY_BASE_URL in .env)Authentication (for calling apps)
X-Proxy-Api-Key: <app-specific-key> on every request to protected endpoints.proxy_clients table.401 and JSON {"error": "..."}.Endpoints
X-Proxy-Api-Key: <app-key> required.Content-Type: application/jsonX-Proxy-Api-Key: <key>{
"amount": "100.00",
"currency": "SZL", // optional, default SZL
"externalId": "ORDER-12345", // app's own order/reference (required)
"payer": {
"partyIdType": "MSISDN",
"partyId": "26876123456" // E.164 without + (required)
},
"payerMessage": "...", // optional
"payeeNote": "...", // optional
"callbackUrl": "https://..." // optional per-request override
}
referenceId is the proxy-generated UUID used as MTN X-Reference-Id.{ "error": "..." }.transactions row with status PENDING.MomoClient::requestToPay() using a proxy callback of PROXY_BASE_URL/callback.audit_log via AuditLogger.Example curl:
curl -X POST https://momopay.swazigist.co.za/request-to-pay \
-H "Content-Type: application/json" \
-H "X-Proxy-Api-Key: <your-app-key>" \
-d '{
"amount":"150.00",
"externalId":"ORDER-98765",
"payer":{"partyIdType":"MSISDN","partyId":"26876123456"}
}'
X-Proxy-Api-Key required.referenceId – the UUID returned by /request-to-pay (or the app can pass its externalId in callbacks; see callback handling notes).transactions DB row.PENDING, it attempts a live poll to MTN via MomoClient::getTransactionStatus() and updates DB if MTN reports a final status.Example curl:
curl -H "X-Proxy-Api-Key: <your-app-key>" \
https://momopay.swazigist.co.za/status/<referenceId>
status, externalId, referenceId, financialTransactionId, reason.transactions row by externalId first, falling back to referenceId.transactions row (mtn_financial_tx_id, failure_reason, mtn_raw_callback).proxy_clients.callback_url) or the per-request callbackUrl if provided when the payment was initiated.proxy_clients.api_key. The header used is X-MoMo-Proxy-Signature: <hex-hmac>.proxy_clients is configured correctly).Forwarding signature verification (for app developers):
api_key.$rawBody = file_get_contents('php://input');
$expected = hash_hmac('sha256', $rawBody, 'YOUR_APP_API_KEY');
$received = $_SERVER['HTTP_X_MOMO_PROXY_SIGNATURE'] ?? '';
if (!hash_equals($expected, $received)) { http_response_code(403); exit; }
$payload = json_decode($rawBody, true);
Schema
Reference-only schema (read-only): the proxy already maintains these tables. Use this section to understand fields returned by API endpoints and callback payloads.
proxy_clients - registered client applications
id (INT) - primary keyclient_name (VARCHAR(100)) - human-friendly nameapi_key (VARCHAR(64)) - app-specific API key (presented as X-Proxy-Api-Key)callback_url (VARCHAR(512)) - default callback URL for forwarded MTN callbacksis_active (TINYINT) - active flag (1 = active)created_at, updated_at (DATETIME)mtn_token_cache - cached MTN bearer tokens
id (INT) - primary keyaccess_token (TEXT) - MTN bearer tokentoken_type (VARCHAR) - token type (usually access_token)expires_at (DATETIME) - expiry timestampcreated_at (DATETIME)transactions - each initiated request-to-pay
id (INT) - primary keyreference_id (CHAR(36)) - proxy-generated UUID used as MTN X-Reference-Idclient_id (INT) - FK → proxy_clients.idapp_callback_url (VARCHAR(512)) - per-request callback override (if provided)external_id (VARCHAR(100)) - the calling app's order/referencepayer_msisdn (VARCHAR(20)) - charged mobile number (E.164 without +)amount (DECIMAL) - requested amountcurrency (CHAR(3)) - ISO currency (default SZL)payer_message, payee_note (VARCHAR) - optional text fieldsstatus (ENUM) - PENDING, SUCCESSFUL, FAILED, TIMEOUT, ONGOING, CANCELLEDmtn_financial_tx_id (VARCHAR) - MTN's financial transaction id (from callback)failure_reason (VARCHAR) - optional human-friendly failure reasoncallback_forwarded (TINYINT) - whether proxy forwarded MTN callback to appcallback_forward_at (DATETIME) - when forward occurredcallback_response_code (SMALLINT) - HTTP status returned by app callbackmtn_raw_callback (JSON) - raw MTN callback bodycreated_at, updated_at (DATETIME)audit_log - HTTP audit records
id (BIGINT) - primary keyreference_id (CHAR(36)) - optional link to transactions.reference_iddirection (ENUM) - one of: INBOUND_FROM_APP, OUTBOUND_TO_MTN, INBOUND_FROM_MTN, OUTBOUND_TO_APPhttp_method (VARCHAR) - HTTP methodurl (VARCHAR) - request URL or endpointrequest_body (JSON) - JSON request recorded for forensicsresponse_code (SMALLINT) - HTTP response coderesponse_body (JSON) - JSON response recordedduration_ms (INT) - round-trip time in millisecondscreated_at (DATETIME)Note: This schema is provided for reference only. The proxy manages these tables; integrating apps should treat them as read-only externally.
MTN Integration details
MTN_API_USER and MTN_API_KEY and Ocp-Apim-Subscription-Key (see MomoClient::refreshToken).Authorization: Bearer <token>, and the Ocp-Apim-Subscription-Key, X-Reference-Id, X-Target-Environment, and X-Callback-Url headers (see MomoClient::requestToPay)..env (MTN_API_USER, MTN_API_KEY, MTN_SUBSCRIPTION_KEY, MTN_BASE_URL, MTN_TARGET_ENV).Error handling & status mapping
/request-to-pay returns 202 when MTN accepted the request (MTN usually returns 202). If MTN returns 200, proxy treats this as a likely duplicate and returns a 502 with explanatory message.transactions.status ENUM values: PENDING, SUCCESSFUL, FAILED, TIMEOUT, ONGOING, CANCELLED.Security recommendations
.env out of webroot; never commit .env to VCS.api_key values (64 hex chars or similar) for proxy_clients.X-MoMo-Proxy-Signature on forwarded callbacks using HMAC-SHA256 and your api_key.api_key s and MTN credentials periodically.Integration checklist for an app (e.g. APP)
proxy_clients row for your app and copy the generated api_key into your app config./request-to-pay with X-Proxy-Api-Key and required JSON fields.referenceId and poll /status/{referenceId} as needed.X-MoMo-Proxy-Signature.Example flow (end-to-end)
/request-to-pay with externalId, amount, payer and X-Proxy-Api-Key.X-Reference-Id: <referenceId> and X-Callback-Url: https://proxy/callback.{ referenceId, status: PENDING } to the app./callback when the transaction settles.api_key.Troubleshooting & operational notes
audit_log table for debugging inbound/outbound requests and timing information.PENDING transactions accumulate, check MTN token health and cron jobs; call /status/{referenceId} to poll MTN.mtn_token_cache to ensure token refreshes are happening.Examples, SDKs & Test Scripts (embedded)
All example clients, verification helpers, the OpenAPI spec, and test scripts are included below so you only need this single documentation file. Copy any snippet directly into your app or CI runner.
OpenAPI (openapi.yaml)
openapi: 3.0.1
info:
title: MoMo Proxy API
version: 1.0.0
description: MTN Collections proxy used by apps to initiate requests-to-pay, poll status, and receive forwarded callbacks.
servers:
- url: https://momopay.swazigist.co.za
security:
- ProxyApiKey: []
components:
securitySchemes:
ProxyApiKey:
type: apiKey
in: header
name: X-Proxy-Api-Key
schemas:
Payer:
type: object
properties:
partyIdType:
type: string
example: MSISDN
partyId:
type: string
example: 26876123456
required: [partyIdType, partyId]
RequestToPay:
type: object
properties:
amount:
type: string
example: "150.00"
currency:
type: string
example: SZL
externalId:
type: string
example: ORDER-12345
payer:
$ref: '#/components/schemas/Payer'
payerMessage:
type: string
payeeNote:
type: string
callbackUrl:
type: string
required: [amount, externalId, payer]
RequestToPayResponse:
type: object
properties:
referenceId:
type: string
format: uuid
status:
type: string
example: PENDING
StatusResponse:
type: object
properties:
referenceId:
type: string
externalId:
type: string
status:
type: string
amount:
type: number
currency:
type: string
payer:
type: string
financialTxId:
type: string
failureReason:
type: string
callbackForwarded:
type: boolean
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
paths:
/request-to-pay:
post:
summary: Initiate a request-to-pay
security:
- ProxyApiKey: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RequestToPay'
responses:
'202':
description: Accepted and processed
content:
application/json:
schema:
$ref: '#/components/schemas/RequestToPayResponse'
'401':
description: Unauthorized
'422':
description: Unprocessable entity (validation error)
/status/{referenceId}:
get:
summary: Poll transaction status
security:
- ProxyApiKey: []
parameters:
- name: referenceId
in: path
required: true
schema:
type: string
responses:
'200':
description: Current status of the transaction
content:
application/json:
schema:
$ref: '#/components/schemas/StatusResponse'
'403':
description: Forbidden (transaction not owned by caller)
'404':
description: Not found
/callback:
post:
summary: MTN posts transaction callback here (forwarded to app)
description: |
MTN will call this endpoint. Proxy will persist and forward to the originating app.
requestBody:
content:
application/json:
schema:
type: object
responses:
'200':
description: OK (proxy always returns 200 to MTN)
PHP client example
Save as php_client.php or run directly on any PHP-enabled machine.
<?php
// Example PHP client: initiate a Request-to-Pay
$proxyBase = getenv('PROXY_BASE') ?: 'https://momopay.swazigist.co.za';
$apiKey = getenv('PROXY_API_KEY') ?: 'APP_CHANGE_THIS';
$payload = [
'amount' => '150.00',
'currency' => 'SZL',
'externalId' => 'ORDER-'.rand(1000,9999),
'payer' => [
'partyIdType' => 'MSISDN',
'partyId' => '26876123456',
],
'payerMessage' => 'Payment for demo',
];
$ch = curl_init($proxyBase . '/request-to-pay');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Proxy-Api-Key: ' . $apiKey,
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$res = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($res === false) {
echo 'cURL error: ' . curl_error($ch) . PHP_EOL;
} else {
echo "HTTP $code\n";
echo $res . PHP_EOL;
}
curl_close($ch);
Node.js client example
Run with Node.js (install axios first: npm install axios).
// Example Node.js client using axios
// Run: node node_client.js
const axios = require('axios');
const PROXY_BASE = process.env.PROXY_BASE || 'https://momopay.swazigist.co.za';
const API_KEY = process.env.PROXY_API_KEY || 'APP_CHANGE_THIS';
(async () => {
try {
const payload = {
amount: '150.00',
currency: 'SZL',
externalId: 'ORDER-' + Math.floor(Math.random()*9000 + 1000),
payer: { partyIdType: 'MSISDN', partyId: '26876123456' },
payerMessage: 'Node demo',
};
const res = await axios.post(PROXY_BASE + '/request-to-pay', payload, {
headers: {
'Content-Type': 'application/json',
'X-Proxy-Api-Key': API_KEY,
},
timeout: 30000,
});
console.log('Status:', res.status);
console.log(res.data);
} catch (err) {
if (err.response) {
console.error('HTTP', err.response.status, err.response.data);
} else {
console.error(err.message);
}
}
})();
Callback verification (PHP)
Place this code in your app's callback endpoint to verify the X-MoMo-Proxy-Signature header.
<?php
// Example verification of forwarded callback in your app
// Place this in your app to verify X-MoMo-Proxy-Signature
$raw = file_get_contents('php://input');
$received = $_SERVER['HTTP_X_MOMO_PROXY_SIGNATURE'] ?? '';
$apiKey = getenv('PROXY_API_KEY') ?: 'APP_CHANGE_THIS';
$expected = hash_hmac('sha256', $raw, $apiKey);
if (!hash_equals($expected, $received)) {
http_response_code(403);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
$payload = json_decode($raw, true);
// Process payment status: $payload['referenceId'], $payload['status']
http_response_code(200);
echo json_encode(['ok' => true]);
cURL Examples
Initiate Request-to-Pay:
curl -X POST https://momopay.swazigist.co.za/request-to-pay \
-H "Content-Type: application/json" \
-H "X-Proxy-Api-Key: <YOUR_APP_KEY>" \
-d '{
"amount":"150.00",
"externalId":"ORDER-1234",
"payer":{"partyIdType":"MSISDN","partyId":"26876123456"}
}'
Poll status:
curl -H "X-Proxy-Api-Key: <YOUR_APP_KEY>" \
https://momopay.swazigist.co.za/status/<referenceId>
Simulate MTN callback to the proxy (for local testing):
curl -X POST https://momopay.swazigist.co.za/callback \
-H "Content-Type: application/json" \
-d '{
"externalId":"ORDER-1234",
"status":"SUCCESSFUL",
"financialTransactionId":"FTX-123456"
}'
Test scripts (bash)
Use these small scripts to run quick smoke tests. They assume curl and jq are installed. Set environment variables before running:
export PROXY_BASE=https://momopay.swazigist.co.za
export API_KEY=APP_CHANGE_THIS
request_to_pay_test.sh - initiate a request-to-pay and print JSON response#!/usr/bin/env bash
set -euo pipefail
PROXY_BASE=${PROXY_BASE:-https://momopay.swazigist.co.za}
API_KEY=${API_KEY:-APP_CHANGE_THIS}
resp=$(curl -s -w "\n%{http_code}" -X POST "$PROXY_BASE/request-to-pay" \
-H "Content-Type: application/json" \
-H "X-Proxy-Api-Key: $API_KEY" \
-d '{"amount":"10.00","externalId":"TEST-'$RANDOM'","payer":{"partyIdType":"MSISDN","partyId":"26876123456"}}')
body=$(echo "$resp" | sed '$d')
code=$(echo "$resp" | tail -n1)
echo "HTTP $code"
echo "$body" | jq
status_test.sh - poll status for a given referenceId#!/usr/bin/env bash
set -euo pipefail
if [ "$#" -lt 1 ]; then
echo "Usage: $0 <referenceId>"
exit 1
fi
PROXY_BASE=${PROXY_BASE:-https://momopay.swazigist.co.za}
API_KEY=${API_KEY:-APP_CHANGE_THIS}
REF=$1
curl -s -H "X-Proxy-Api-Key: $API_KEY" "$PROXY_BASE/status/$REF" | jq
send_callback_sim.sh - simulate an MTN callback to the proxy#!/usr/bin/env bash
set -euo pipefail
if [ "$#" -lt 1 ]; then
echo "Usage: $0 <externalId-or-referenceId>"
exit 1
fi
PROXY_BASE=${PROXY_BASE:-https://momopay.swazigist.co.za}
ID=$1
payload=$(printf '{"externalId":"%s","status":"SUCCESSFUL","financialTransactionId":"FTX-%s"}' "$ID" "$RANDOM")
echo "Sending callback to $PROXY_BASE/callback with payload: $payload"
curl -i -X POST "$PROXY_BASE/callback" \
-H "Content-Type: application/json" \
-d "$payload"