Section 01

Authentication

All API requests must be authenticated using a custom HMAC-SHA256 signature. Include these headers with every request.

Base URL https://portal.paymeafrica.com/api/v1
Header Description
Content-Type Required Must be application/json
X-App-ID Required Your unique App Identifier (e.g., MOBILE_APP_01)
X-Timestamp Required Current Unix timestamp (integer). Used for signature and replay attack protection.
X-Signature Required Base64 encoded HMAC‑SHA256 hash of MinifiedJSONBody + Timestamp using your App Secret.
X-Sandbox Optional Set to 1 to run in sandbox mode. Transactions are simulated and balance is isolated.

Replay Protection: Requests with a timestamp older than 5 minutes will be automatically rejected. Always use the current Unix timestamp.

Limits

Rate Limits

Requests are throttled per client IP and, for transaction submission specifically, also per X-App-ID. Limits apply independently and both can trigger a 429 response.

Scope Applies to Limit On repeated violation
Per IP All /api/v1/* endpoints 30 requests / minute 429 until the window resets, no suspension
Per X-App-ID POST /api/v1/transact only 10 requests / 10 seconds 3 violations within 2 minutes → that App ID is suspended for 15 minutes (every request returns 429 until it expires)

The per-App-ID limit exists because transact is the one endpoint that triggers a real, synchronous call to the upstream mobile money provider for every request. A burst of concurrent calls from one integration doesn't just throttle that integration, it can degrade the upstream connection for every client on the platform, so it's enforced more strictly and with a cooldown, not just a rolling window.

Field Description
status "error"
message Human-readable reason (rate-limited vs. temporarily suspended)
retry_after Seconds until you may retry (present on the per-request rate-limit response; not on the suspension response, which lasts the full 15 minutes)

Best practice: queue and serialize transact calls on your side rather than firing them concurrently, and back off on a 429 instead of retrying immediately. If your integration has a legitimate need for sustained throughput above these limits, contact support to discuss a higher per-App-ID allowance.

Overview

Integration Flows

Understand the end-to-end process for the most common integration scenarios.

1. Mobile Collection (Push)

Initiate payment → Customer receives USSD prompt → Enters PIN → You receive webhook confirmation.

2. Disbursement (Payout)

Check balance → Lookup Recipient (optional) → Submit Payout → Funds delivered instantly.

3. Card Tokenization & Pay

Redirect to Hosted Checkout → Save Card → Receive Token → Charge Token later for one-tap payments.

4. VCN Lifecycle

Submit Details → Issue Card → Show Details (Secure) → User pays online → Funds deducted from your app balance.

Section 02

Collection (Receive Money)

Initiate a USSD push payment to a customer's mobile wallet. The customer will receive a prompt on their handset to confirm payment.

POST /api/v1/transact
Parameter Type Description
action Required string Must be "collection"
amount Required integer Amount in TZS. Must be greater than 0.
msisdn Required string Customer's phone number in international format (e.g., 255700000000)
reference Required string Your unique transaction reference. Must be globally unique per client.
callback_url Optional string URL to receive webhook notifications for this specific transaction.
Request Payload
{
  "action": "collection",
  "amount": 1000,
  "msisdn": "255700000000",
  "reference": "ORDER_101",
  "callback_url": "https://yourapp.com/callback"
}
Sample Response
{
  "status": "success",
  "transaction_id": "901",
  "payment_status": "PENDING",
  "provider_response": {
    "result": "SUCCESS",
    "resultcode": "000",
    "message": "Payment notification logged"
  }
}
Section 03

Disbursement (Send Money)

Send funds to various channels. Use the channel field to route the disbursement to the correct network or bank.

Section 04

Card Payments & Checkout

There are two ways to process card payments: using a Hosted Checkout (for new cards) or Stored Card Tokens (for one-tap payments).

Important: Card payments with no billing info will be rejected by Selcom. Ensure all billing_* fields are provided.

Hosted Checkout (New Cards)

Use the checkout action to receive a payment_gateway_url where the customer can enter their card details.

POST /api/v1/transact
Hosted Checkout Request
{
  "action": "checkout",
  "amount": 10000,
  "reference": "INV-1001",
  "billing_firstname": "John",
  "billing_lastname": "Doe",
  "billing_address_1": "969 Market Street",
  "billing_city": "San Francisco",
  "billing_state_or_region": "CA",
  "billing_postcode_or_pobox": "94103",
  "billing_country": "US",
  "billing_phone": "255000000000",
  "redirect_url": "https://yourapp.com/success",
  "cancel_url": "https://yourapp.com/cancel"
}

Stored Card Payment (One-tap)

Use the card-payment action with a previously obtained card_token.

Stored Card Request
{
  "action": "card-payment",
  "amount": 5000,
  "reference": "INV-202",
  "card_token": "CT_987654321",
  "buyer_userid": "user_123",
  "gateway_buyer_uuid": "GB_uuid_001"
}
Section 05

Card Management

List and delete stored card tokens for a user. This allows you to provide a "saved cards" experience in your checkout flow.

List Stored Cards

GET /api/v1/cards
Parameter Description
buyer_userid Required The internal ID of your customer.
gateway_buyer_uuid Optional Specific UUID if known.
Sample Response
{
  "status": "success",
  "data": [
    {
      "card_token": "CT_987654321",
      "card_number": "411111XXXXXX1111",
      "expiry": "12/26",
      "card_type": "VISA"
    }
  ]
}

Delete Card

DELETE /api/v1/cards/{id}

Parameter in URL: id (The index or card token to remove)

Section 06

Virtual Card Network (VCN)

Issue and manage secure virtual cards for your customers. These cards can be used for online payments worldwide.

Issue New VCN

POST /api/v1/vcn/create
Request Payload
{
  "msisdn": "255700000000",
  "email": "user@example.com",
  "name": "John Doe"
}

Change VCN Status

POST /api/v1/vcn/status
Field Value
status "BLOCK" or "UNBLOCK"

Show Full Card Details

POST /api/v1/vcn/show

⚠️ Secure Endpoint: Only call when the user enters their PIN on your UI.

Get VCN Details & Balance

GET /api/v1/vcn/query
POST /api/v1/transact

Mobile Wallet & Selcom Pesa

Mobile Wallet Request
{
  "action": "disbursement",
  "amount": 5000,
  "msisdn": "255700000000",
  "channel": "CASHIN",
  "reference": "PAYOUT_001"
}
Sample Response
{
  "status": "success",
  "transaction_id": "902",
  "payment_status": "COMPLETED",
  "financials": {
    "system_profit": 55,
    "provider_fee": 345,
    "total_deductible": 5400
  }
}

Bank Transfer (Qwiksend)

Parameter Type Description
action Required string "disbursement"
channel Required string "BANK" for Qwiksend bank transfer
amount Required integer Amount in TZS
msisdn Required string Sender's phone number
recipient_bank_code Required string Bank code (e.g., CRDB). See Channels section.
recipient_account Required string Recipient's bank account number
recipient_name Optional string Recipient's full name
remarks Optional string Transaction memo or notes
reference Required string Your unique transaction reference
Bank Transfer Request
{
  "action": "disbursement",
  "channel": "BANK",
  "amount": 50000,
  "msisdn": "255700000000",
  "recipient_bank_code": "CRDB",
  "recipient_account": "01J12345678900",
  "recipient_name": "John Doe",
  "reference": "SALARY_BATCH_01",
  "remarks": "February Salary"
}
Section 07

Name Lookup

Validate recipient details before sending funds. Supports wallet (mobile money), bank account, and Selcom Pesa lookups.

POST /api/v1/lookup

Wallet Lookup

Request
{
  "type": "wallet",
  "channel": "vodacom",
  "msisdn": "255754000000"
}

Bank Account Lookup

Request
{
  "type": "bank",
  "bank_code": "CRDB",
  "account_number": "01J12345678900"
}

Sample Response

Response
{
  "status": "success",
  "name": "Jane Doe",
  "original_data": { ... }
}
Section 08

Transaction Query

Check the current status of a transaction using your internal reference. For pending transactions, the middleware will automatically attempt a live status check with the provider.

POST /api/v1/query
Request
{
  "reference": "ORDER_101"
}
Response
{
  "status": "SUCCESS",
  "reference": "ORDER_101",
  "amount": 1000,
  "currency": "TZS",
  "payment_status": "COMPLETED",
  "created_at": "2024-01-15 10:30:00",
  "provider_checked": true,
  "provider_message": null
}

If payment_status is still PENDING, check provider_checked: true means we successfully reached the provider and the transaction is genuinely still pending; false means the live check itself failed (see provider_message) and you should retry shortly rather than treating it as confirmed.

Live provider checks are rate-limited per transaction: at most one every 30 seconds, and only for transactions created within the last hour. Outside those limits the stored status is returned with provider_checked: false. Older pending transactions are still resolved automatically by the provider callback and our background reconciliation, and you'll receive your callback_url webhook when they are, so there's no need to keep polling them.

Section 09

Account Summary

Retrieve a financial summary for your application including total volume and transaction counts, with a breakdown by collections vs. disbursements the two have unrelated failure modes and shouldn't be read as one blended number.

POST /api/v1/summarize
Request
{} // No body required
Response
{
  "status": "SUCCESS",
  "data": {
    "collections": {
      "total_count": 150,
      "total_volume": 3800000,
      "success_count": 140,
      "pending_count": 2,
      "failed_count": 8,
      "delivered_count": 148
    },
    "disbursements": { "...same shape" },
    "account_balance": 3150000
  }
}

account_balance is your current available app balance (successful collections minus successful/pending disbursements and fees), scoped to live or sandbox depending on whether the X-Sandbox header was sent. Collections and disbursements are always reported separately there is no combined total, since the two have unrelated failure modes.

success_count is the real completed-payment count (status = 'success') the same thing account_balance is computed from. delivered_count counts requests that left our system and were accepted by the provider for collections that means the payment prompt actually reached the customer's phone; for disbursements it means the provider processed the payout. Neither field is redefined by this endpoint; that distinction only affects how the admin/portal dashboards label a number for humans, not this data contract. Rate/percentage fields (delivery rate, completion rate) are temporarily removed from this response while their definitions are finalized.

Section 10

Webhooks (Callbacks)

Payme Africa does not require pre-registration of webhooks. Instead, you must provide a callback_url in each /transact request. The gateway will send an asynchronous POST request to this URL when the transaction reaches a final state.

Status Descriptions

Field Value Description
result "SUCCESS" The final outcome of the transaction was successful.
result "FAILED" The transaction failed at the provider or was cancelled.
payment_status "PENDING" Awaiting customer action (PIN entry).
payment_status "COMPLETED" Funds have been successfully transferred.
payment_status "FAILED" The payment process stopped due to an error.

Sample Webhook Payload

POST → your callback URL
{
  "transid": "7945454515",
  "reference": "ORDER_101",
  "result": "SUCCESS",
  "payment_status": "COMPLETED",
  "amount": "1000",
  "msisdn": "255700000000"
}

Verifying Webhook Signatures

To ensure the webhook came from the middleware, you must verify the X-Middleware-Signature header.

Algorithm: Base64( HMAC_SHA256( Payload + X-Timestamp, Your_Shared_Secret ) )

PHP Verification Example
$payload   = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'];
$signature = $_SERVER['HTTP_X_MIDDLEWARE_SIGNATURE'];

$expected  = base64_encode(
    hash_hmac('sha256', $payload . $timestamp, $secret, true)
);

if (hash_equals($signature, $expected)) {
    // ✓ Verified process the webhook safely
}

Testing Webhooks: Use the Webhook Testing tool in the Developer Portal to fire a simulated test_ping event to your registered URL without needing a real transaction.

Section 11

Transaction Result Codes

Common result codes returned in API responses and webhook payloads.

Code Description Action Required
000 Success Transaction completed successfully. Deliver value.
021 Pending Transaction initiated. Wait for callback or poll /api/v1/query.
009 Failed Generic failure. Check account status or entered details.
052 Insufficient Balance Customer has insufficient funds in their wallet.
056 User Cancelled Customer cancelled the USSD push request.
INS_FUNDS Insufficient App Balance Your middleware application balance is too low for this disbursement (fees included).
SERVICE_DISABLED Service Not Enabled The requested service (collection/disbursement) is disabled for your application.
PROVIDER_TIMEOUT Outcome Unknown (HTTP 202) The provider received the request but did not answer in time. The transaction is left PENDING and may still complete. Do not retry with a new reference: wait for your callback_url or poll /api/v1/query.
PROVIDER_UNAVAILABLE Provider Unreachable (HTTP 503) The request never reached the provider and the transaction is FAILED. Safe to retry shortly with a new reference.
OUT_OF_RANGE Amount Out of Range The requested amount is outside the min/max allowed for this service tier.
Section 12

Supported Channels & Banks

Use these codes in the channel and recipient_bank_code request fields.

Mobile Networks

Channel Code Network / Provider
CASHIN Universal Any carrier (auto-detected)
vodacom M-Pesa (Vodacom)
airtel Airtel Money
tigo Tigo Pesa (MIC Tanzania)
halotel HaloPesa
zantel EzyPesa (Zantel)
ttcl T-Pesa (TTCL)
SELCOM_PESA Selcom Pesa

Popular Banks (Qwiksend)

Bank Code Bank Name
CRDB CRDB Bank
NMB NMB Bank
NBC NBC Bank
EXIM Exim Bank
STANBIC Stanbic Bank
ABSA Absa Bank
DTB Diamond Trust Bank
KCB KCB Bank