Merchant API Documentation

Server-to-server API for card and mobile money payments

Base URL: http://localhost:3000

Authentication

Create API keys in the merchant portal at /m/api-keys. The plaintext key and secret are shown once at creation.

Include both headers on every request:

x-api-key: pk_...
x-api-secret: sk_...
Security: API keys are for server-to-server identification only. Never embed them in mobile apps or browser code.

Try It – Card API

Sign in to your merchant account to use the interactive testing console.

Processing Modes

Card processing supports three modes: 2D, 3D, and AUTO.

  • AUTO (recommended): Attempts 3D first, falls back to 2D if declined.
  • 3D: Always requires 3D Secure authentication. Returns a redirectUrl.
  • 2D: No redirect; status returned immediately.

Card Payments

Create Payment

POST /api/v1/payments

Creates a new card payment. For 3DS flows, returns a redirectUrl.

Required fields

  • amount, currency
  • customer: firstName, lastName, email
  • billing: firstName, lastName, street, city, country, postCode
  • card: holder, number, expMonth, expYear, securityCode

Optional fields

  • returnUrl — Where to redirect after 3DS
  • reference — Your order/invoice reference
  • description — Payment description

Example

curl -X POST http://localhost:3000/api/v1/payments \
  -H 'Content-Type: application/json' \
  -H 'x-api-key: pk_...' \
  -H 'x-api-secret: sk_...' \
  -d '{
    "amount": 10.00,
    "currency": "USD",
    "reference": "order_123",
    "returnUrl": "https://yoursite.com/payment/complete",
    "customer": {
      "firstName": "John",
      "lastName": "Doe",
      "email": "john@example.com"
    },
    "billing": {
      "firstName": "John",
      "lastName": "Doe",
      "street": "123 Main St",
      "city": "New York",
      "country": "US",
      "postCode": "10001"
    },
    "card": {
      "holder": "JOHN DOE",
      "number": "4111111111111111",
      "expMonth": "12",
      "expYear": "30",
      "securityCode": "123"
    }
  }'

Response

{
  "id": "tx_...",
  "reference": "order_123",
  "status": "INITIATED",
  "redirectUrl": "https://gateway.example.com/3ds/...",
  "returnUrl": "https://yoursite.com/payment/complete",
  "createdAt": "2026-02-16T10:30:00.000Z"
}

Force 3D Secure

POST /api/v1/payments/3d

Same request body as /api/v1/payments, but always forces 3DS authentication.

Mobile Money (APM) Payments

Accept mobile money payments across Africa. The API initiates a charge request and sends a USSD/STK push notification to the customer's phone for authorization.

Create APM Payment

POST /api/v1/apm/payments

Required fields

  • amount — Payment amount (number > 0)
  • currency — ISO currency code (KES, GHS, TZS, etc.)
  • customer.firstName, customer.lastName
  • customer.email
  • customer.phoneCode — Country calling code (e.g., 254)
  • customer.phone — Phone number without country code

Optional fields

  • reference — Your order reference (auto-generated if omitted)
  • description — Payment description

Example

curl -X POST http://localhost:3000/api/v1/apm/payments \
  -H 'Content-Type: application/json' \
  -H 'x-api-key: pk_...' \
  -H 'x-api-secret: sk_...' \
  -d '{
    "amount": 1500,
    "currency": "KES",
    "reference": "order_123",
    "description": "Mobile money payment",
    "customer": {
      "firstName": "John",
      "lastName": "Doe",
      "email": "john@example.com",
      "phoneCode": "254",
      "phone": "712345678"
    }
  }'

Response

{
  "id": "apm_1707425123456_abc123",
  "pexiTransactionId": "PEXI1707425123456ABCD",
  "reference": "order_123",
  "status": "PENDING",
  "createdAt": "2026-02-16T10:30:00.000Z",
  "chargeRequestId": "6605",
  "customer": {
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@example.com",
    "phoneCode": "254",
    "phone": "712345678"
  }
}

Supported Countries

Kenya (KES)

M-Pesa, Airtel Money

Ghana (GHS)

MTN MoMo, AirtelTigo, Vodafone Cash

Tanzania (TZS)

Vodacom M-Pesa, Airtel, Tigo

Rwanda (RWF)

MTN MoMo, Airtel Money

Zambia (ZMW)

MTN MoMo, Airtel Money

Senegal (XOF)

Orange Money, Free Money

Sierra Leone (SLE)

Orange Money, Africell Money

Integration Flow

  1. Call POST /api/v1/apm/payments with customer details and amount
  2. Customer receives USSD/STK push on their phone
  3. Customer authorizes payment by entering their PIN
  4. Webhook notification sent to your configured URL
  5. Update order status based on webhook (status 700 = success, 701 = failed)

Webhook Status Codes

  • 700 — Payment successful (CAPTURED)
  • 701 — Payment failed (FAILED)
  • 702 — Payment reversed (REFUNDED)
  • 703 — Payment pending (PROCESSING)

Mobile Money Payouts

Disburse funds to mobile money accounts across 12 African countries. Perfect for salary payments, vendor payments, and marketplaces.

Create Payout

POST /api/v1/apm/payouts

Initiates a payout to a beneficiary's mobile money account. Funds are typically received within 1-5 minutes.

Required fields

  • amount — Payout amount (number > 0)
  • currency — ISO currency code (KES, XOF, GHS, etc.)
  • beneficiary.firstName, beneficiary.lastName
  • beneficiary.phoneCode — Country calling code (e.g., 254)
  • beneficiary.phone — Phone number without country code

Optional fields

  • beneficiary.email — Beneficiary email address
  • reference — Your payout reference (auto-generated if omitted)
  • description — Payout description

Example Request

curl -X POST http://localhost:3000/api/v1/apm/payouts \
  -H 'Content-Type: application/json' \
  -H 'x-api-key: pk_...' \
  -H 'x-api-secret: sk_...' \
  -d '{
    "amount": 1500,
    "currency": "KES",
    "reference": "salary_march_2026",
    "description": "Monthly salary payment",
    "beneficiary": {
      "firstName": "Jane",
      "lastName": "Doe",
      "email": "jane@example.com",
      "phoneCode": "254",
      "phone": "740334661"
    }
  }'

Response

{
  "id": "payout_1234567890_abc123",
  "reference": "salary_march_2026",
  "status": "PENDING",
  "createdAt": "2026-03-06T15:00:00Z",
  "payoutRequestId": "987654321",
  "beneficiary": {
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane@example.com",
    "phoneCode": "254",
    "phone": "740334661"
  }
}

Supported Countries

🇰🇪 Kenya (KES)

M-Pesa, Airtel Money

🇸🇳 Senegal (XOF)

Orange Money, Free Money

🇸🇱 Sierra Leone (SLE)

Orange Money, Africell

🇬🇭 Ghana (GHS)

MTN, Airtel, Vodafone

🇹🇿 Tanzania (TZS)

Vodacom, Airtel, Tigo

🇷🇼 Rwanda (RWF)

MTN, Airtel

🇿🇲 Zambia (ZMW)

MTN, Airtel, Zamtel

🇲🇼 Malawi (MWK)

Airtel, TNM

🇨🇲 Cameroon (XAF)

MTN, Orange

🇧🇯 Benin (XOF)

MTN, Moov

🇨🇮 Côte d'Ivoire (XOF)

MTN, Orange, Wave, Moov

🇨🇩 DR Congo (CDF)

Vodacom, Airtel, Orange

Payout Flow

  1. Call POST /api/v1/apm/payouts with beneficiary details and amount
  2. Payout is submitted to LIPAD for processing
  3. Beneficiary receives funds in their mobile money account (1-5 minutes)
  4. Webhook notification sent to your configured URL with final status
  5. Update payout records based on webhook (status 700 = success, 701 = failed)

Payout Statuses

  • PENDING — Payout submitted, awaiting processing
  • CAPTURED — Payout completed successfully (status code 700)
  • FAILED — Payout failed (status code 701)

Webhook Payload

When a payout completes or fails, you'll receive a webhook notification:

{
  "txId": "payout_1234567890_abc123",
  "reference": "salary_march_2026",
  "status": "CAPTURED",
  "gateway": {
    "disbursement_request_id": "987654321",
    "external_reference": "salary_march_2026",
    "event": "successful_payout",
    "payment_status": 700,
    "amount": "1500",
    "beneficiary_msisdn": "+254740334661",
    "beneficiary_name": "Jane Doe",
    "payment_method_code": "MPESA_KEN",
    "transaction_id": "QAB123XYZ",
    "payment_date": "2026-03-06T15:05:00Z"
  }
}

💡 Best Practices

  • Always use unique references for each payout
  • Validate phone numbers before submission (E.164 format)
  • Use webhooks for final status confirmation
  • Monitor your LIPAD account balance before sending payouts
  • Store payout records for reconciliation and audit trails

⚠️ Important Notes

  • Payouts cannot be canceled once submitted
  • Maximum payout amount varies by provider (e.g., KES 150,000 for M-Pesa)
  • Ensure MERCHANT_APM role is enabled for your merchant account
  • Some countries require LIPAD service codes - contact support for setup

Try It – APM API

Sign in to your merchant account to test mobile money payments.

Transactions

List Card Transactions

GET /api/v1/transactions

Query params

  • limit — Default 20, max 100
  • cursor — For pagination
  • status — Filter by status
  • from, to — Date range (ISO format)

Get Card Transaction

GET /api/v1/transactions/:id

Add ?includeRaw=1 to include full gateway response.

List APM Transactions

GET /api/v1/apm/transactions

Returns mobile money transactions with same query params as card transactions.

Get APM Transaction

GET /api/v1/apm/transactions/:id

Webhooks

PexiPay sends webhooks to notify your server about payment status changes in real-time.

Why Use Webhooks?

  • Customers may close the browser before returning to your site
  • Network issues can interrupt return URL redirects
  • Enable automated order fulfillment and real-time updates

Configuration

Configure your webhook URL in the merchant dashboard at /m/settings.

Card Payment Webhook Payload

{
  "txId": "tx_abc123...",
  "status": "SUCCEEDED",
  "amount": 10.00,
  "currency": "USD",
  "reference": "order_123",
  "merchantId": "cm_...",
  "timestamp": "2026-02-16T10:30:00.000Z",
  "cardBrand": "VISA",
  "cardLast4": "1111",
  "customer": {
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@example.com"
  }
}

APM Payment Webhook Payload

{
  "event": "successful_payment",
  "amount": "1500",
  "currency_code": "KES",
  "payer_msisdn": "254712345678",
  "payer_email": "john@example.com",
  "payment_status": 700,
  "charge_request_id": "123456789",
  "external_reference": "order_123",
  "payment_method_code": "MPESA_KEN",
  "transaction_id": "2019",
  "payer_transaction_id": "RBL35TKMUH",
  "payment_date": "2026-02-16T10:30:00.000Z"
}

Card Payment Statuses

  • SUCCEEDED — Payment completed
  • DECLINED — Declined by issuing bank
  • FAILED — Processing error
  • CANCELLED — Customer cancelled
  • PENDING — Awaiting final status

Implementation Example

app.post('/webhooks/pexipay', async (req, res) => {
  // Acknowledge receipt immediately
  res.status(200).json({ received: true });

  const { txId, status, reference } = req.body;

  switch (status) {
    case 'SUCCEEDED':
      await fulfillOrder(reference);
      break;
    case 'DECLINED':
    case 'FAILED':
      await notifyPaymentFailed(reference);
      break;
  }
});

Timeouts & Retries

PexiPay expects a 200 response within 5 seconds. Failed webhooks are retried with exponential backoff.

Error Codes

  • 400 — Invalid request parameters
  • 401 — Missing or invalid API credentials
  • 403 — Merchant not authorized for this payment type
  • 404 — Resource not found
  • 502 — Payment gateway error
  • 503 — Service temporarily unavailable

Postman Collection

Download and import for quick testing.

Set environment variables apiKey and apiSecret from your merchant portal.