Payment Service Provider

Mamlaka
Payment API

Accept and send payments via mobile money, card, and bank transfer. Integrate seamlessly with M-Pesa, card gateways, and bank APIs across Africa.

REST API

JSON over HTTPS with JWT authentication

Callbacks

Async notifications for payment status

Multi-Channel

M-Pesa, Card, and Bank Transfer support

Base URL
https://payments.mamlakapsp.com/api/v1

Authentication

All authenticated endpoints require an Authorization: Bearer <token> header. First generate a token using Basic Auth via the endpoint.

PUBLICBASIC AUTHBEARER

Key Response Fields

secureIdEncrypted transaction identifier for secure reference
externalIdYour merchant-provided reference ID
transactionStatusFinal state: COMPLETE, FAILED, or PENDING
transactionReportDetailed status report
netAmountAmount received after deductions/fees
currencyTransaction currency code (KES, USD, NGN, etc.)

Authentication

Obtain a JWT token required for all subsequent API operations. Use Basic Auth with your merchant credentials.

GET/api/v1BASIC

Generate Token

Obtain a JWT token using your merchant credentials via Basic Authentication. This token is required for all other API calls.

Request

cURL
curl -X GET 'https://payments.mamlakapsp.com/api/v1' \
  -H 'Authorization: Basic <credentials>'

Response

Success (200)
1234
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 3600
}

Mobile Money

Initiate STK Push payments (C2B) and money transfers (B2C) via mobile money. Set `mobileMoneySP` to "M-PESA" or "airtel" to choose the provider — the same endpoints handle both, so switching a transaction to Airtel is a one-field change. On success, the callback's `reference` field carries the provider receipt (M-Pesa or Airtel). When callback signing is enabled for your account, each callback carries an `X-Mamlaka-Signature: sha256=<hex HMAC-SHA256(rawBody, yourSecret)>` header — verify it against the shared secret Mamlaka issued you, using the exact raw request bytes and a constant-time comparison. Note: Airtel's minimum amount is 10 KES.

POST/api/v1/mobile/initiatePUBLIC

STK Push (C2B)

Initiate an STK push to request payment from a customer's mobile money account. The customer receives a prompt on their phone to authorize the payment. Authenticated by your impalaMerchantId in the body — no bearer token is required. Supports M-Pesa and Airtel — set `mobileMoneySP` to "M-PESA" or "airtel". The result (COMPLETE/FAILED) is delivered via callback once the customer responds.

Parameters

ParameterTypeRequiredDescription
impalaMerchantIdstringYesYour merchant username/ID
displayNamestringYesBusiness name displayed to the customer on STK prompt
currencystringYesCurrency code (e.g. "KES")
amountnumberYesAmount to charge
payerPhonestringYesCustomer phone number in international format (e.g. "254...")
mobileMoneySPstringYesProvider: "M-PESA" or "airtel" (case-insensitive). Airtel minimum is 10 KES.
externalIdstringYesYour unique reference for this transaction
callbackUrlstringYesURL to receive payment status callbacks

Request

Request Body
12345678910
{
  "impalaMerchantId": "your_merchant_id",
  "displayName": "Your Business Name",
  "currency": "KES",
  "amount": 10,
  "payerPhone": "254712345678",
  "mobileMoneySP": "M-Pesa",
  "externalId": "order_12345",
  "callbackUrl": "https://your-app.com/callback"
}

Response

Success (200)
12345
{
  "message": "Payment initiation successful",
  "secureId": "qdml8553ZeInavKorBHzLA==",
  "transactionId": "order_12345"
}
POST/api/v1/mobile/transferBEARER

Money Transfer (B2C)

Transfer money from your merchant account to a customer's mobile money account. Supports M-Pesa and Airtel — set `mobileMoneySP` to "M-PESA" or "airtel". Airtel disbursements typically settle immediately (COMPLETE in the response path); M-Pesa settles via callback.

Parameters

ParameterTypeRequiredDescription
impalaMerchantIdstringYesYour merchant username/ID
currencystringYesCurrency code (e.g. "KES")
amountnumberYesAmount to transfer
recipientPhonestringYesRecipient phone number in international format (e.g. "254...")
mobileMoneySPstringYesProvider: "M-PESA" or "airtel" (case-insensitive). Airtel minimum is 10 KES.
externalIdstringYesYour unique reference for this transaction
callbackUrlstringYesURL to receive transfer status callbacks

Request

Request Body
123456789
{
  "impalaMerchantId": "your_merchant_id",
  "currency": "KES",
  "amount": 10,
  "recipientPhone": "254712345678",
  "mobileMoneySP": "M-Pesa",
  "externalId": "transfer_12345",
  "callbackUrl": "https://your-app.com/callback"
}

Response

Success (200)
12345
{
  "message": "Payment initiation successful",
  "secureId": "qdml8553ZeInavKorBHzLA==",
  "transactionId": "a392-45d1-93c1-58f9447915e717138558"
}

Card Payments

Process card payments through a secure hosted payment page. Customers are redirected to complete payment.

POST/api/v1/card/initiateBEARER

Initiate Card Payment

Process card payments through a secure payment gateway. Returns a hosted payment link for the customer to complete the transaction.

Parameters

ParameterTypeRequiredDescription
impalaMerchantIdstringYesYour merchant username/ID
currencystringYesCurrency code (e.g. "USD")
amountnumberYesAmount to charge
mobileMoneySPstringYesMust be "card"
externalIdstringYesYour unique reference for this transaction
redirectUrlstringYesURL to redirect customer after payment completes
callbackUrlstringYesURL to receive payment status callbacks

Request

Request Body
123456789
{
  "impalaMerchantId": "your_merchant_id",
  "currency": "USD",
  "amount": 1,
  "mobileMoneySP": "card",
  "externalId": "card_order_12345",
  "redirectUrl": "https://your-app.com/payment-complete",
  "callbackUrl": "https://your-app.com/callback"
}

Response

Success (200)
12345
{
  "cardLink": "https://payments.mamlakapsp.com/pay?data=...",
  "message": "card Payment initiation successful",
  "secureId": "OWXJJDUMgPvh9XQhSBy9xg=="
}

Bank Transfer

Receive payments via bank transfer. Generate virtual account details for customers to pay into.

POST/api/v1/bank/payinBEARER

Bank Transfer (Payin)

Receive payments via bank transfer from customers. Generates temporary bank account details for the customer to transfer funds to.

Parameters

ParameterTypeRequiredDescription
externalIdstringYesYour unique reference for this transaction
amountnumberYesAmount to collect
currencystringYesCurrency code (e.g. "NGN")
callbackUrlstringYesURL to receive payment status callbacks
customerNamestringYesName of the paying customer
customerEmailstringYesEmail of the paying customer
accountNamestringOptionalLabel shown to the payer (defaults to "Payment")

Request

Request Body
123456789
{
  "externalId": "bank_order_12345",
  "amount": 100,
  "currency": "NGN",
  "accountName": "Demo account",
  "callbackUrl": "https://your-app.com/callback",
  "customerName": "John Doe",
  "customerEmail": "johndoe@gmail.com"
}

Response

Success (200)
12345678910111213
{
  "secureId": "PSJRU873PGPI",
  "externalId": "bank_order_12345",
  "status": "pending",
  "message": "Bank transfer initiated. Customer should pay to the account below.",
  "amount": 100,
  "currency": "NGN",
  "bankAccountName": "Demo account",
  "bankAccountNumber": "5010875892",
  "bankName": "vfd",
  "bankCode": "566",
  "expiryDate": "2026-03-14T01:26:36.263Z"
}

Balance

Check your merchant account balances for payins and payouts.

GET/api/v1/balance/payinsBEARER

Payins Balance

Retrieve the current balance of funds received from customers (payins).

Request

cURL
curl -X GET 'https://payments.mamlakapsp.com/api/v1/balance/payins' \
  -H 'Authorization: Bearer <token>'

Response

Success (200)
1234
{
  "balance": 15000,
  "currency": "KES"
}
GET/api/v1/balance/payoutsBEARER

Payouts Balance

Retrieve the current balance available for payouts to customers.

Request

cURL
curl -X GET 'https://payments.mamlakapsp.com/api/v1/balance/payouts' \
  -H 'Authorization: Bearer <token>'

Response

Success (200)
1234
{
  "balance": 8500,
  "currency": "KES"
}

Transactions

Query the status of any transaction using its secure ID or external reference.

GET/api/v1/transaction?reference={externalId}PUBLIC

Transaction Status

Look up a transaction's status. Pass the reference as a query parameter: `?reference=<value>`, where the value is your externalId (recommended), or the secureId, merchantRequestID, or checkoutRequestID. Optionally add `&merchant=<impalaMerchantId>`, but it must exactly match the merchant the transaction was booked under — if unsure, omit it. Note: the M-Pesa receipt number is NOT a lookup key. Do not query Safaricom directly (TSQ) — collections settle on Mamlaka's short code, so Safaricom returns "not found"; Mamlaka is the system of record.

Request

cURL
curl -X GET 'https://payments.mamlakapsp.com/api/v1/transaction?reference={externalId}'

Response

Success (200)
12345678910111213
{
  "transaction": {
    "impalaMerchantId": "your_merchant_id",
    "transaction_status": "COMPLETE",
    "transaction_report": "collection",
    "currency": "KES",
    "amount": 10,
    "secure_id": "qdml8553ZeInavKorBHzLA==",
    "external_id": "order_12345",
    "callback_url": "https://your-app.com/callback",
    "date_added": 1787138041
  }
}

Callbacks & Signature

How Mamlaka notifies your callbackUrl of final transaction status, and how to verify the X-Mamlaka-Signature header on every callback.

Callback payload

When a transaction reaches a final state we send an HTTP POST with a JSON body to the callbackUrl you supplied. Respond 200 OK to acknowledge. On success, reference carries the mobile-money receipt number (M-Pesa or Airtel); match on your externalId. The callback shape and signature are identical for M-Pesa and Airtel.

POST → your callbackUrl
123456789
{
  "amount": 10,
  "currency": "KES",
  "externalId": "order_12345",
  "secureId": "i1FHTQgFpHP0g-MRacZXUQ==",
  "reference": "UHJM32U1U5",
  "transactionStatus": "COMPLETE",
  "transactionReport": "collection"
}

Signature verification — X-Mamlaka-Signature

When callback signing is enabled for your account, every callback carries an X-Mamlaka-Signature header. It is an HMAC-SHA256 of the exact raw request body, keyed with the shared secret Mamlaka issued you, hex-encoded:

Header
X-Mamlaka-Signature: sha256=<hex HMAC-SHA256(rawBody, yourSecret)>

Recompute the HMAC over the body as received(before any JSON re-serialization — re-stringifying can change the bytes and break the signature) and compare with a constant-time comparison:

Verify (Node.js)
12345678910111213141516171819202122
const crypto = require('crypto')

function verifyMamlakaSignature(rawBody, header, secret) {
  const expected = 'sha256=' +
    crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
  const a = Buffer.from(header || '', 'utf8')
  const b = Buffer.from(expected, 'utf8')
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

// Express: capture the RAW body so the bytes match what we signed
app.use('/callbacks/mamlaka', express.raw({ type: '*/*' }))

app.post('/callbacks/mamlaka', (req, res) => {
  const raw = req.body // Buffer
  if (!verifyMamlakaSignature(raw, req.get('X-Mamlaka-Signature'), process.env.MAMLAKA_CALLBACK_SECRET)) {
    return res.sendStatus(401)
  }
  const payload = JSON.parse(raw.toString('utf8'))
  // reconcile using payload.externalId + payload.reference ...
  res.sendStatus(200)
})