Lyntra Payments API

Welcome to the Lyntra Payments API documentation. Our REST API lets you integrate mobile money collections and disbursements via MTN Mobile Money and Airtel Money across Uganda.

๐Ÿ“ Base URL Production: https://api.lyntra.com/v1  ยท  Staging: https://staging.lyntraengineering.com/v1

How the API Works

All requests use standard HTTP methods (GET, POST, PATCH, DELETE) and return JSON responses. Authentication uses API keys passed in the Authorization header.

Platform-managed Providers MTN and Airtel provider credentials are configured at the platform level by Lyntra โ€” you do not need to supply or manage API credentials for the mobile money providers. The correct provider is selected automatically based on the customer's phone number prefix.

Request Format

POST /v1/collections/initiate HTTP/1.1
Host: api.lyntra.com
Authorization: Bearer pgk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

{"msisdn": "256771234567", "amount": "10000", ...}

Response Format

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "reference": "LYN_20240326_XYZ789",
  "status": "PENDING",
  "amount": "10000",
  "currency": "UGX",
  "created_at": "2024-03-26T10:00:00Z"
}

Error Format

{
  "error": {
    "code": "INVALID_MSISDN",
    "message": "The provided phone number is not a valid MTN number",
    "request_id": "req_abc123"
  }
}

Authentication

All API requests require an API key passed in the HTTP Authorization header as a Bearer token.

โš ๏ธ Keep your API keys secret Never expose API keys in client-side code, git repositories, or public channels. Use environment variables.

API Key Types

  • Sandbox keys โ€” prefix pgk_sandbox_ โ€” safe for testing, no real money moves
  • Production keys โ€” prefix pgk_live_ โ€” live transactions, real money

Scopes

API keys have scopes that control what operations they can perform:

ScopeDescription
collections:writeInitiate payment collections
collections:readView collection transactions
disbursements:writeInitiate disbursements
disbursements:readView disbursement transactions
balance:readView account balances
kyc:readCheck KYC / account verification status

Example Header

Authorization: Bearer pgk_sandbox_b9ac76b458a85f8bd15d9e276a14bb8f7c3f127a498386593b5b124023e96d60

Getting API Keys

API keys are generated from your dashboard under API Keys. You can create multiple keys with different scopes and environments. Production keys require KYC verification.

Quick Start

Get your first payment collection running in under 5 minutes.

Step 1 โ€” Get your API key

Log in to your dashboard, navigate to API Keys, and create a sandbox key with collections:write scope.

Step 2 โ€” Get your Business Account ID

Go to Business Accounts in your dashboard and copy the ID of the account you want to receive payments into. Business accounts act as virtual wallets โ€” credits from collections and debits from disbursements are tracked against them.

Step 3 โ€” Initiate a collection

The correct mobile money provider (MTN or Airtel) is automatically detected from the customer's phone number โ€” you do not need to specify it.

Node.js
Python
PHP
cURL
const API_KEY = 'pgk_sandbox_your_key_here';
const ACCOUNT_ID = 'your_business_account_id';

const res = await fetch('https://staging.lyntraengineering.com/v1/collections/initiate', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    msisdn: '256771234567',   // MTN auto-detected from 0771 prefix
    amount: '5000',           // Amount in UGX
    currency: 'UGX',
    reference: 'ORDER-1234',   // Your unique reference
    business_account_id: ACCOUNT_ID,
    description: 'Payment for Order #1234'
  })
});
const data = await res.json();
console.log(data.reference); // Save this to check status later
import requests

API_KEY = 'pgk_sandbox_your_key_here'
ACCOUNT_ID = 'your_business_account_id'

r = requests.post(
    'https://staging.lyntraengineering.com/v1/collections/initiate',
    headers={'Authorization': f'Bearer {API_KEY}'},
    json={
        'msisdn': '256771234567',   # MTN auto-detected from 0771 prefix
        'amount': '5000',
        'currency': 'UGX',
        'reference': 'ORDER-1234',
        'business_account_id': ACCOUNT_ID,
        'description': 'Payment for Order #1234'
    }
)
data = r.json()
print(data['reference'])  # Save to check status
$apiKey = 'pgk_sandbox_your_key_here';
$accountId = 'your_business_account_id';

$ch = curl_init('https://staging.lyntraengineering.com/v1/collections/initiate');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer {$apiKey}", 'Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
        'msisdn' => '256771234567',   // MTN auto-detected
        'amount' => '5000',
        'currency' => 'UGX',
        'reference' => 'ORDER-1234',
        'business_account_id' => $accountId,
        'description' => 'Payment for Order #1234'
    ])
]);
$data = json_decode(curl_exec($ch), true);
echo $data['reference']; // Save to check status
curl -X POST https://staging.lyntraengineering.com/v1/collections/initiate \
  -H "Authorization: Bearer pgk_sandbox_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "msisdn": "256771234567",
    "amount": "5000",
    "currency": "UGX",
    "reference": "ORDER-1234",
    "business_account_id": "your_account_id",
    "description": "Payment for Order #1234"
  }'

Step 4 โ€” Handle the webhook

When the customer pays (or the transaction fails), Lyntra sends a POST request to your callback_url:

{
  "event": "collection.successful",
  "data": {
    "reference": "LYN_20240326_XYZ789",
    "status": "SUCCESSFUL",
    "amount": "5000",
    "msisdn": "256771234567",
    "provider_txn_id": "MTN_TXN_987654"
  },
  "timestamp": "2024-03-26T10:05:23Z",
  "signature": "sha256=abc123..."
}

Sandbox Testing

โœ… Sandbox Environment Use https://staging.lyntraengineering.com/v1 with keys prefixed pgk_sandbox_. No real money moves.

The sandbox mimics the full production behaviour. Use these test numbers to simulate different outcomes:

Phone NumberProviderSimulated Outcome
256771234567MTNSUCCESSFUL
256772000001MTNFAILED โ€” Insufficient funds
256772000002MTNFAILED โ€” Number not registered
256701234567AIRTELSUCCESSFUL
256702000001AIRTELFAILED โ€” Timeout

Set environment: "SANDBOX" in all requests during testing. Switch to "PRODUCTION" when you go live.

Collections

Collections let you request a payment from a customer's mobile money wallet. The customer receives a USSD prompt to approve the payment.

POST /v1/collections/initiate Initiate a collection โ–พ

Sends a mobile money debit prompt to the customer's phone. The response returns immediately with a PROCESSING status. The final result is delivered via webhook. The mobile money provider (MTN or Airtel) is automatically selected based on the customer's phone number prefix โ€” no provider configuration required.

Request Body

ParameterTypeRequiredDescription
msisdnstringrequiredCustomer's phone number. Format: 256XXXXXXXXX. Provider auto-detected from prefix.
amountstringrequiredAmount in the specified currency (e.g. "5000")
business_account_idstring (UUID)requiredThe business account to receive the funds
currencystringoptionalCurrency code. Defaults to "UGX"
referencestringoptionalYour own unique reference ID for idempotency. Auto-generated if omitted.
descriptionstringoptionalPayment description shown to the customer

Response

202 Accepted 400 Bad Request 401 Unauthorized 422 Validation Error
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "reference": "LYN_20240326_ABC123",
  "status": "PENDING",
  "type": "COLLECTION",
  "provider": "MTN",
  "msisdn": "256771234567",
  "amount": "5000",
  "currency": "UGX",
  "fee": "75",
  "net_amount": "4925",
  "initiated_at": "2024-03-26T10:00:00Z"
}
GET /v1/collections/{reference} Get collection by reference โ–พ

Retrieve the current status and details of a collection transaction.

Path Parameters

ParameterTypeDescription
referencestringThe transaction reference returned from the initiate call

Response

{
  "reference": "LYN_20240326_ABC123",
  "status": "SUCCESSFUL",
  "provider_txn_id": "MTN_TXN_987654",
  "completed_at": "2024-03-26T10:02:45Z",
  "amount": "5000", "fee": "75", "net_amount": "4925"
}
GET /v1/collections?page=1&page_size=50 List all collections โ–พ

Returns a paginated list of all collection transactions for your business.

Query Parameters

ParameterTypeDefaultDescription
pageinteger1Page number
page_sizeinteger50Results per page (max 200)
statusstringโ€”Filter by status: PENDING, SUCCESSFUL, FAILED, CANCELLED
providerstringโ€”Filter by provider: MTN, AIRTEL

Disbursements

Disbursements let you send money to any MTN or Airtel mobile money number. Use this for payroll, refunds, agent commissions, and bulk payouts.

POST /v1/disbursements/initiate Send money to a phone number โ–พ

The mobile money provider is automatically selected from the recipient's phone number โ€” no additional configuration needed.

Request Body

ParameterTypeRequiredDescription
msisdnstringrequiredRecipient's phone number (256XXXXXXXXX). Provider auto-detected from prefix.
amountstringrequiredAmount to send
business_account_idstring (UUID)requiredThe business account to debit
currencystringoptionalDefaults to "UGX"
referencestringoptionalYour unique reference for idempotency
recipient_namestringoptionalRecipient's name for records
descriptionstringoptionalTransaction description

Code Examples

Node.js
Python
PHP
const res = await fetch('https://api.lyntra.com/v1/disbursements/initiate', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    msisdn: '256771234567',    // MTN auto-detected from 0771 prefix
    amount: '10000',
    currency: 'UGX',
    reference: 'SALARY-MAR-2024-001',
    business_account_id: accountId,
    recipient_name: 'John Doe',
    description: 'Salary payment March 2024'
  })
});
r = requests.post('https://api.lyntra.com/v1/disbursements/initiate',
    headers={'Authorization': f'Bearer {api_key}'},
    json={
        'msisdn': '256771234567',    # MTN auto-detected
        'amount': '10000',
        'currency': 'UGX',
        'reference': 'SALARY-MAR-2024-001',
        'business_account_id': account_id,
        'recipient_name': 'John Doe',
        'description': 'Salary payment'
    })
$res = json_decode(file_get_contents('https://api.lyntra.com/v1/disbursements/initiate',
  false, stream_context_create(['http' => [
    'method'  => 'POST',
    'header'  => "Authorization: Bearer {$apiKey}\r\nContent-Type: application/json",
    'content' => json_encode([
      'msisdn'            => '256771234567',    // MTN auto-detected
      'amount'            => '10000',
      'currency'          => 'UGX',
      'reference'         => 'SALARY-MAR-2024-001',
      'business_account_id' => $accountId,
      'recipient_name'    => 'John Doe',
    ])]])), true);
GET /v1/disbursements/{reference} Get disbursement by reference โ–ธ

Retrieve the current status of a disbursement by your reference. Also accessible by internal UUID at /v1/disbursements/txn/{id}.

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "reference": "SALARY-MAR-2024-001",
  "status": "SUCCESSFUL",
  "provider": "MTN",
  "msisdn": "256771234567",
  "amount": "10000",
  "currency": "UGX",
  "provider_txn_id": "MTN_TXN_123456",
  "completed_at": "2024-03-26T10:04:12Z"
}
GET /v1/disbursements?page=1&page_size=50 List all disbursements โ–ธ

Returns a paginated list of all disbursement transactions for your business.

Query Parameters

ParameterTypeDefaultDescription
pageinteger1Page number
page_sizeinteger50Results per page (max 200)
statusstringโ€”Filter: PENDING, PROCESSING, SUCCESSFUL, FAILED
providerstringโ€”Filter: MTN, AIRTEL
POST /v1/disbursements/{reference}/sync Force status sync from provider โ–ธ

Manually polls the provider for the current disbursement status and updates the transaction record. Useful if a callback was missed.

200 OK 404 Not Found

KYC โ€” Know Your Customer

KYC endpoints let you verify mobile money subscribers before initiating transactions. Results are cached for 24 hours to reduce provider calls. Requires the kyc:read scope.

Caching KYC results are cached per subscriber. Repeat lookups within 24 hours return the cached result without hitting the mobile money provider.
GET /v1/kyc/{msisdn} Full KYC lookup โ–พ

Returns full subscriber information from the mobile money provider. The provider is auto-detected from the phone number prefix.

Path Parameters

ParameterTypeDescription
msisdnstringPhone number in format 256XXXXXXXXX

Response

{
  "msisdn": "256771234567",
  "first_name": "John",
  "last_name": "Doe",
  "gender": "M",
  "birthdate": "1990-01-15",
  "is_active": true,
  "is_barred": false,
  "is_pin_set": true,
  "registration_status": "REGISTERED",
  "provider": "MTN"
}
200 OK 400 Unsupported Network 401 Unauthorized 403 Missing kyc:read scope
GET /v1/kyc/{msisdn}/validate Quick transaction eligibility check โ–ธ

Returns a lightweight response indicating whether the subscriber can transact. Active, non-barred subscribers return can_transact: true.

{
  "msisdn": "256771234567",
  "can_transact": true,
  "is_active": true,
  "is_barred": false
}

Business Accounts

Business accounts are virtual wallets that track your transaction flows. Credits from successful collections and debits from disbursements are recorded against the account's shadow ledger.

No provider setup required You do not configure MTN or Airtel credentials on a business account. Provider configurations are managed at the platform level by Lyntra and shared across all accounts.
GET /v1/business-accounts List business accounts โ–ธ

Returns all business accounts for your company.

[
  {
    "id": "ba_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "name": "Main Operations",
    "currency": "UGX",
    "is_active": true,
    "created_at": "2024-03-26T09:00:00Z"
  }
]
POST /v1/business-accounts Create a business account โ–ธ
ParameterTypeRequiredDescription
namestringrequiredAccount display name
currencystringoptionalDefaults to "UGX"
descriptionstringoptionalInternal description
PATCH /v1/business-accounts/{id} Update a business account โ–ธ

Update the name, description, or active status of an account.

ParameterTypeDescription
namestringNew display name
descriptionstringUpdated description
is_activebooleanSet to false to deactivate

Balance

GET /v1/balance Get all account balances โ–พ

Returns shadow ledger balances for a business account, broken down by provider. Requires balance:read scope. Use /v1/balance/{business_account_id} to query a specific account.

{
  "business_account_id": "ba_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "account_name": "Main Operations",
  "currency": "UGX",
  "providers": [
    {
      "provider": "MTN",
      "account_type": "COLLECTION",
      "shadow_balance": 2500000
    },
    {
      "provider": "MTN",
      "account_type": "DISBURSEMENT",
      "shadow_balance": 750000
    },
    {
      "provider": "AIRTEL",
      "account_type": "COLLECTION",
      "shadow_balance": 980000
    }
  ]
}

Webhooks

Lyntra Payments sends real-time webhook events to your callback_url when transaction statuses change. This is the recommended way to track payment outcomes.

๐Ÿ’ก Pro Tip Always verify the webhook signature before processing. Never trust an unverified webhook.

Webhook Events

EventDescription
collection.successfulCustomer paid successfully
collection.failedCollection failed (declined, timeout, etc.)
collection.cancelledCustomer cancelled the prompt
disbursement.successfulMoney sent successfully
disbursement.failedDisbursement failed

Webhook Payload

{
  "event": "collection.successful",
  "data": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "reference": "LYN_20240326_ABC123",
    "status": "SUCCESSFUL",
    "type": "COLLECTION",
    "provider": "MTN",
    "msisdn": "256771234567",
    "amount": "5000",
    "currency": "UGX",
    "fee": "75",
    "net_amount": "4925",
    "provider_txn_id": "MTN_TXN_987654",
    "completed_at": "2024-03-26T10:02:45Z"
  },
  "timestamp": "2024-03-26T10:02:46Z",
  "signature": "sha256=4ac3f7e89b1234567890abcdef123456"
}

Verifying Webhook Signatures

Each webhook includes a signature header computed as HMAC-SHA256(payload, webhook_secret). Always verify this before processing.

Node.js
Python
PHP
const crypto = require('crypto');

function verifyWebhook(rawBody, signatureHeader, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected), Buffer.from(signatureHeader)
  );
}

// Express.js example
app.post('/webhooks', express.raw({type: 'application/json'}), (req, res) => {
  const sig = req.headers['x-lyntra-signature'];
  if (!verifyWebhook(req.body, sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body);
  // Process event...
  res.sendStatus(200);
});
import hmac, hashlib

def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

# Flask example
from flask import Flask, request, abort
app = Flask(__name__)

@app.route('/webhooks', methods=['POST'])
def webhook():
    sig = request.headers.get('X-Lyntra-Signature', '')
    if not verify_webhook(request.data, sig, WEBHOOK_SECRET):
        abort(401)
    event = request.get_json()
    # Process event...
    return '', 200
function verifyWebhook($rawBody, $signature, $secret): bool {
    $expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
    return hash_equals($expected, $signature);
}

$rawBody = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_LYNTRA_SIGNATURE'] ?? '';

if (!verifyWebhook($rawBody, $sig, $webhookSecret)) {
    http_response_code(401);
    exit('Invalid signature');
}
$event = json_decode($rawBody, true);
// Process $event...
http_response_code(200);
โš ๏ธ Idempotency Webhooks may be delivered more than once. Always check if you've already processed an event using the data.id field before acting on it.

Error Codes

HTTPCodeDescription
400INVALID_MSISDNPhone number format is invalid
400INVALID_AMOUNTAmount must be a positive number
400UNSUPPORTED_PROVIDERProvider not supported for this number
401UNAUTHORIZEDMissing or invalid API key
403INSUFFICIENT_SCOPESAPI key lacks required scope
403IP_BLOCKEDRequest from non-whitelisted IP
404NOT_FOUNDTransaction or resource not found
409DUPLICATE_REFERENCEA transaction with this reference already exists
422VALIDATION_ERRORRequest body failed validation
429RATE_LIMIT_EXCEEDEDToo many requests. Retry after the specified delay
502PROVIDER_ERRORError from mobile money provider
503PROVIDER_UNAVAILABLEProvider temporarily unavailable

API Keys

Create and manage API keys from your dashboard under API Keys. Each key has a set of scopes and is bound to an environment (sandbox or production).

POST /v1/api-keys Create an API key โ–ธ

Requires a valid JWT session (login first). The raw key is only returned once at creation โ€” store it securely.

ParameterTypeRequiredDescription
namestringrequiredDisplay label for the key
environmentstringrequired"SANDBOX" or "PRODUCTION"
scopesstring[]requiredList of scopes, e.g. ["collections:write","collections:read"]
{
  "id": "3fa85f64-...",
  "name": "Production Key",
  "raw_key": "pgk_live_xxxxxxxxxxxxxxxxxxxxxxxx",  // Only shown once!
  "key_prefix": "pgk_live_xxxx",
  "scopes": ["collections:write", "collections:read"],
  "environment": "PRODUCTION",
  "is_active": true
}

Providers & Networks

The correct mobile money provider is automatically selected based on the customer's phone number prefix. You never specify a provider explicitly in collection or disbursement requests.

PrefixProviderNetworkCollectionsDisbursementsKYC
2567 (070x)AIRTELAirtel Moneyโœ…โœ…โœ…
2567 (071x)MTNMTN Mobile Moneyโœ…โœ…โœ…
2567 (077x)MTNMTN Mobile Moneyโœ…โœ…โœ…
2567 (075x)AIRTELAirtel Moneyโœ…โœ…โœ…
Otherโ€”UnsupportedโŒโŒโŒ
Unsupported Networks Requests with an unrecognised phone number prefix return 422 UNSUPPORTED_NETWORK. Always validate the MSISDN format before initiating a transaction.

Platform-Managed Credentials

MTN and Airtel API credentials (client ID, client secret, subscription keys) are stored at the platform level. All business accounts share these platform provider configurations โ€” there is no per-company or per-account credential management.

Transaction Statuses

StatusMeaningTerminal?
PENDINGTransaction initiated, waiting for customerNo
PROCESSINGProvider is processingNo
SUCCESSFULTransaction completed successfullyโœ… Yes
FAILEDTransaction failedโœ… Yes
CANCELLEDCancelled by customerโœ… Yes
EXPIREDCustomer didn't respond within time limitโœ… Yes

SDKs & Libraries

Official SDKs coming soon. Until then, use the REST API directly with the code examples above.

๐Ÿ“ฆ
Node.js
Coming soon
๐Ÿ
Python
Coming soon
๐Ÿ˜
PHP
Coming soon
๐Ÿ“ฎ
Postman

Changelog

v1.1 โ€” March 2026

  • Platform-level provider configs โ€” MTN and Airtel credentials are now managed centrally by Lyntra. Companies no longer configure per-account provider credentials.
  • Automatic provider detection โ€” The provider and environment fields have been removed from collection and disbursement request bodies. The correct provider is selected automatically from the phone number prefix.
  • KYC endpoints โ€” New GET /v1/kyc/{msisdn} for full subscriber lookup and GET /v1/kyc/{msisdn}/validate for quick transaction eligibility checks. Results cached for 24 hours.
  • Disbursement sync โ€” New POST /v1/disbursements/{reference}/sync endpoint to manually poll the provider for the latest status.
  • Per-provider balance breakdown โ€” GET /v1/balance/{business_account_id} now returns balances split by provider and account type (collection vs disbursement).

v1.0 โ€” January 2026

  • Initial release of Lyntra Payments API
  • MTN Mobile Money collections and disbursements
  • Airtel Money collections and disbursements
  • Real-time webhooks with HMAC signature verification
  • Sandbox environment for testing
  • Business account management
  • Scoped API keys