K-Pay API Documentation

Complete guide to integrating K-Pay payment gateway into your application.

Security: K-Pay is ISO/IEC 27001:2013 and PCI-DSS Certified

Getting Started

The K-Pay API provides programmatic access to accept payments via Mobile Money (MTN, Airtel), Visa, Mastercard, American Express, SmartCash, and SPENN.

Base URL
https://pay.esicia.com/
Integration Steps
  1. Register for a developer account
  2. Receive your test API credentials
  3. Complete all required API tests
  4. Request production access
  5. Start accepting payments!

Authentication

All API requests must include two authentication headers:

Header Type Description
Kpay-Key String Your API key
Authorization String Basic Auth: Basic base64(username:password)
Content-Type String application/json
Authentication Examples
<?php
$api_key = 'your_api_key';
$username = 'your_username';
$password = 'your_password';

$headers = [
    'Content-Type: application/json',
    'Kpay-Key: ' . $api_key,
    'Authorization: Basic ' . base64_encode($username . ':' . $password)
];

$ch = curl_init('https://pay.esicia.com/');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
?>
curl -X POST https://pay.esicia.com/ \
  -H "Content-Type: application/json" \
  -H "Kpay-Key: your_api_key" \
  -H "Authorization: Basic $(echo -n 'username:password' | base64)"
const axios = require('axios');

const apiKey = 'your_api_key';
const username = 'your_username';
const password = 'your_password';

const headers = {
    'Content-Type': 'application/json',
    'Kpay-Key': apiKey,
    'Authorization': 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64')
};

const client = axios.create({
    baseURL: 'https://pay.esicia.com',
    headers: headers
});
import java.net.http.*;
import java.util.Base64;

String apiKey = "your_api_key";
String username = "your_username";
String password = "your_password";

String auth = Base64.getEncoder().encodeToString(
    (username + ":" + password).getBytes()
);

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://pay.esicia.com/"))
    .header("Content-Type", "application/json")
    .header("Kpay-Key", apiKey)
    .header("Authorization", "Basic " + auth)
    .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
    .build();
import 'dart:convert';
import 'package:http/http.dart' as http;

final String apiKey = 'your_api_key';
final String username = 'your_username';
final String password = 'your_password';

final String basicAuth = 'Basic ' + base64Encode(
    utf8.encode('$username:$password')
);

final headers = {
    'Content-Type': 'application/json',
    'Kpay-Key': apiKey,
    'Authorization': basicAuth,
};

final response = await http.post(
    Uri.parse('https://pay.esicia.com/'),
    headers: headers,
    body: jsonEncode(payload),
);
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;

string apiKey = "your_api_key";
string username = "your_username";
string password = "your_password";

var client = new HttpClient();
client.BaseAddress = new Uri("https://pay.esicia.com/");

var byteArray = Encoding.ASCII.GetBytes($"{username}:{password}");
client.DefaultRequestHeaders.Authorization = 
    new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
client.DefaultRequestHeaders.Add("Kpay-Key", apiKey);
client.DefaultRequestHeaders.Accept.Add(
    new MediaTypeWithQualityHeaderValue("application/json"));

Payment Request

Initiate a payment by making a POST request with the following parameters:

Endpoint
POST https://pay.esicia.com/
Parameters
Parameter Required Type Description
action Required string Must be "pay"
msisdn Required string Mobile phone number with country code (no + sign). e.g., 250783300000
email Required string Email of the paying customer
details Required string Description of the payment
refid Required string Unique payment reference from your system
amount Required integer Amount in RWF
currency Optional string Currency code. Default: RWF
cname Required string Customer name
cnumber Required string Customer number (your internal reference)
pmethod Required string Payment method: momo, cc, or spenn
retailerid Required string Your unique retailer ID
returl Required string Callback URL for payment notification (webhook)
redirecturl Required string URL to redirect customer after payment
logourl Optional string URL to your logo for the checkout page
Payment Request Examples
<?php
$api_key = 'your_api_key';
$username = 'your_username';
$password = 'your_password';

$payload = [
    'action' => 'pay',
    'msisdn' => '250783300000',
    'email' => 'customer@example.com',
    'details' => 'Order #12345',
    'refid' => 'ORDER' . time() . rand(1000, 9999),
    'amount' => 5000,
    'currency' => 'RWF',
    'cname' => 'John Doe',
    'cnumber' => 'CUST001',
    'pmethod' => 'momo',
    'retailerid' => 'YOUR_RETAILER_ID',
    'returl' => 'https://yoursite.com/callback',
    'redirecturl' => 'https://yoursite.com/success'
];

$ch = curl_init('https://pay.esicia.com/');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($payload),
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Kpay-Key: ' . $api_key,
        'Authorization: Basic ' . base64_encode($username . ':' . $password)
    ]
]);

$response = curl_exec($ch);
$result = json_decode($response, true);
curl_close($ch);

if ($result['success'] == 1) {
    // Redirect to checkout page
    header('Location: ' . $result['url']);
} else {
    echo 'Error: ' . $result['reply'];
}
?>
curl -X POST https://pay.esicia.com/ \
  -H "Content-Type: application/json" \
  -H "Kpay-Key: your_api_key" \
  -H "Authorization: Basic $(echo -n 'username:password' | base64)" \
  -d '{
    "action": "pay",
    "msisdn": "250783300000",
    "email": "customer@example.com",
    "details": "Order #12345",
    "refid": "ORDER123456789",
    "amount": 5000,
    "currency": "RWF",
    "cname": "John Doe",
    "cnumber": "CUST001",
    "pmethod": "momo",
    "retailerid": "YOUR_RETAILER_ID",
    "returl": "https://yoursite.com/callback",
    "redirecturl": "https://yoursite.com/success"
  }'
const axios = require('axios');

const apiKey = 'your_api_key';
const username = 'your_username';
const password = 'your_password';

async function initiatePayment() {
    const payload = {
        action: 'pay',
        msisdn: '250783300000',
        email: 'customer@example.com',
        details: 'Order #12345',
        refid: 'ORDER' + Date.now(),
        amount: 5000,
        currency: 'RWF',
        cname: 'John Doe',
        cnumber: 'CUST001',
        pmethod: 'momo',
        retailerid: 'YOUR_RETAILER_ID',
        returl: 'https://yoursite.com/callback',
        redirecturl: 'https://yoursite.com/success'
    };

    try {
        const response = await axios.post('https://pay.esicia.com/', payload, {
            headers: {
                'Content-Type': 'application/json',
                'Kpay-Key': apiKey,
                'Authorization': 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64')
            }
        });

        if (response.data.success === 1) {
            console.log('Checkout URL:', response.data.url);
            // Redirect user to response.data.url
        } else {
            console.error('Error:', response.data.reply);
        }
    } catch (error) {
        console.error('Request failed:', error.message);
    }
}

initiatePayment();
import java.net.http.*;
import java.net.URI;
import java.util.Base64;
import org.json.*;

public class KPayPayment {
    private static final String API_KEY = "your_api_key";
    private static final String USERNAME = "your_username";
    private static final String PASSWORD = "your_password";
    
    public static void main(String[] args) throws Exception {
        JSONObject payload = new JSONObject();
        payload.put("action", "pay");
        payload.put("msisdn", "250783300000");
        payload.put("email", "customer@example.com");
        payload.put("details", "Order #12345");
        payload.put("refid", "ORDER" + System.currentTimeMillis());
        payload.put("amount", 5000);
        payload.put("currency", "RWF");
        payload.put("cname", "John Doe");
        payload.put("cnumber", "CUST001");
        payload.put("pmethod", "momo");
        payload.put("retailerid", "YOUR_RETAILER_ID");
        payload.put("returl", "https://yoursite.com/callback");
        payload.put("redirecturl", "https://yoursite.com/success");

        String auth = Base64.getEncoder().encodeToString(
            (USERNAME + ":" + PASSWORD).getBytes()
        );

        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://pay.esicia.com/"))
            .header("Content-Type", "application/json")
            .header("Kpay-Key", API_KEY)
            .header("Authorization", "Basic " + auth)
            .POST(HttpRequest.BodyPublishers.ofString(payload.toString()))
            .build();

        HttpResponse<String> response = client.send(request, 
            HttpResponse.BodyHandlers.ofString());
        
        JSONObject result = new JSONObject(response.body());
        if (result.getInt("success") == 1) {
            System.out.println("Checkout URL: " + result.getString("url"));
        } else {
            System.out.println("Error: " + result.getString("reply"));
        }
    }
}
import 'dart:convert';
import 'package:http/http.dart' as http;

class KPayService {
  final String apiKey = 'your_api_key';
  final String username = 'your_username';
  final String password = 'your_password';
  final String baseUrl = 'https://pay.esicia.com/';

  Future<Map<String, dynamic>> initiatePayment({
    required String phone,
    required String email,
    required String customerName,
    required int amount,
    required String refId,
  }) async {
    final String basicAuth = 'Basic ' + base64Encode(
      utf8.encode('$username:$password')
    );

    final payload = {
      'action': 'pay',
      'msisdn': phone,
      'email': email,
      'details': 'Payment for order',
      'refid': refId,
      'amount': amount,
      'currency': 'RWF',
      'cname': customerName,
      'cnumber': 'CUST001',
      'pmethod': 'momo',
      'retailerid': 'YOUR_RETAILER_ID',
      'returl': 'https://yoursite.com/callback',
      'redirecturl': 'https://yoursite.com/success',
    };

    final response = await http.post(
      Uri.parse(baseUrl),
      headers: {
        'Content-Type': 'application/json',
        'Kpay-Key': apiKey,
        'Authorization': basicAuth,
      },
      body: jsonEncode(payload),
    );

    final result = jsonDecode(response.body);
    
    if (result['success'] == 1) {
      // Navigate to WebView with result['url']
      return {'success': true, 'url': result['url']};
    } else {
      return {'success': false, 'error': result['reply']};
    }
  }
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

public class KPayService
{
    private readonly string _apiKey = "your_api_key";
    private readonly string _username = "your_username";
    private readonly string _password = "your_password";
    private readonly HttpClient _client;

    public KPayService()
    {
        _client = new HttpClient();
        _client.BaseAddress = new Uri("https://pay.esicia.com/");
        
        var byteArray = Encoding.ASCII.GetBytes($"{_username}:{_password}");
        _client.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
        _client.DefaultRequestHeaders.Add("Kpay-Key", _apiKey);
    }

    public async Task<PaymentResponse> InitiatePaymentAsync(PaymentRequest request)
    {
        var payload = new
        {
            action = "pay",
            msisdn = request.Phone,
            email = request.Email,
            details = request.Description,
            refid = request.RefId,
            amount = request.Amount,
            currency = "RWF",
            cname = request.CustomerName,
            cnumber = "CUST001",
            pmethod = request.PaymentMethod,
            retailerid = "YOUR_RETAILER_ID",
            returl = "https://yoursite.com/callback",
            redirecturl = "https://yoursite.com/success"
        };

        var json = JsonSerializer.Serialize(payload);
        var content = new StringContent(json, Encoding.UTF8, "application/json");
        
        var response = await _client.PostAsync("", content);
        var responseBody = await response.Content.ReadAsStringAsync();
        
        return JsonSerializer.Deserialize<PaymentResponse>(responseBody);
    }
}

public class PaymentRequest
{
    public string Phone { get; set; }
    public string Email { get; set; }
    public string CustomerName { get; set; }
    public string Description { get; set; }
    public string RefId { get; set; }
    public int Amount { get; set; }
    public string PaymentMethod { get; set; } = "momo";
}
Success Response
{
    "reply": "PENDING",
    "url": "https://pay.esicia.com/checkout/A12343983489",
    "success": 1,
    "authkey": "m43snbf9oivnmersqh6mn1lbh5",
    "tid": "E6974831594723691",
    "refid": "ORDER123456789",
    "retcode": 0
}
Error Response
{
    "reply": "TARGET_AUTHORIZATION_ERROR",
    "url": "",
    "success": 0,
    "authkey": "m43snbf9oivnmersqh6mn1lbh5",
    "tid": "E6974831594723691",
    "refid": "ORDER123456789",
    "retcode": 606
}

Payment Status Inquiry

Check the status of a payment using the reference ID.

Endpoint
POST https://pay.esicia.com/
Parameters
Parameter Required Type Description
action Required string Must be "checkstatus"
refid Required string Payment reference from your system
Status Check Examples
<?php
$payload = [
    'action' => 'checkstatus',
    'refid' => 'ORDER123456789'
];

$ch = curl_init('https://pay.esicia.com/');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($payload),
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Kpay-Key: ' . $api_key,
        'Authorization: Basic ' . base64_encode($username . ':' . $password)
    ]
]);

$response = curl_exec($ch);
$result = json_decode($response, true);
curl_close($ch);

if ($result['statusid'] === '01') {
    echo 'Payment successful!';
    echo 'Transaction ID: ' . $result['momtransactionid'];
} else {
    echo 'Payment status: ' . $result['statusdesc'];
}
?>
curl -X POST https://pay.esicia.com/ \
  -H "Content-Type: application/json" \
  -H "Kpay-Key: your_api_key" \
  -H "Authorization: Basic $(echo -n 'username:password' | base64)" \
  -d '{
    "action": "checkstatus",
    "refid": "ORDER123456789"
  }'
async function checkPaymentStatus(refId) {
    const payload = {
        action: 'checkstatus',
        refid: refId
    };

    const response = await axios.post('https://pay.esicia.com/', payload, {
        headers: {
            'Content-Type': 'application/json',
            'Kpay-Key': apiKey,
            'Authorization': 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64')
        }
    });

    if (response.data.statusid === '01') {
        console.log('Payment successful!');
        console.log('MoMo Transaction:', response.data.momtransactionid);
    } else {
        console.log('Status:', response.data.statusdesc);
    }
    
    return response.data;
}
public JSONObject checkPaymentStatus(String refId) throws Exception {
    JSONObject payload = new JSONObject();
    payload.put("action", "checkstatus");
    payload.put("refid", refId);

    String auth = Base64.getEncoder().encodeToString(
        (USERNAME + ":" + PASSWORD).getBytes()
    );

    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://pay.esicia.com/"))
        .header("Content-Type", "application/json")
        .header("Kpay-Key", API_KEY)
        .header("Authorization", "Basic " + auth)
        .POST(HttpRequest.BodyPublishers.ofString(payload.toString()))
        .build();

    HttpResponse<String> response = client.send(request, 
        HttpResponse.BodyHandlers.ofString());
    
    JSONObject result = new JSONObject(response.body());
    
    if ("01".equals(result.getString("statusid"))) {
        System.out.println("Payment successful!");
    }
    
    return result;
}
Future<Map<String, dynamic>> checkPaymentStatus(String refId) async {
    final String basicAuth = 'Basic ' + base64Encode(
      utf8.encode('$username:$password')
    );

    final payload = {
      'action': 'checkstatus',
      'refid': refId,
    };

    final response = await http.post(
      Uri.parse(baseUrl),
      headers: {
        'Content-Type': 'application/json',
        'Kpay-Key': apiKey,
        'Authorization': basicAuth,
      },
      body: jsonEncode(payload),
    );

    final result = jsonDecode(response.body);
    
    if (result['statusid'] == '01') {
      return {'success': true, 'data': result};
    } else {
      return {'success': false, 'status': result['statusdesc']};
    }
}
public async Task<StatusResponse> CheckPaymentStatusAsync(string refId)
{
    var payload = new
    {
        action = "checkstatus",
        refid = refId
    };

    var json = JsonSerializer.Serialize(payload);
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    
    var response = await _client.PostAsync("", content);
    var responseBody = await response.Content.ReadAsStringAsync();
    
    var result = JsonSerializer.Deserialize<StatusResponse>(responseBody);
    
    if (result.StatusId == "01")
    {
        Console.WriteLine("Payment successful!");
    }
    
    return result;
}
Success Response
{
    "tid": "A441489693051",
    "refid": "ORDER123456789",
    "momtransactionid": "616730887",
    "statusid": "01",
    "statusdesc": "Successfully processed transaction."
}
Failed Response
{
    "tid": "E6974831594723691",
    "refid": "ORDER123456789",
    "momtransactionid": "12943154",
    "statusdesc": "TARGET_AUTHORIZATION_ERROR",
    "statusmsg": "Not enough funds",
    "statusid": "02"
}

Callback Response (Webhook)

K-Pay will send a POST request to your returl when the payment status changes.

Callback Payload
Parameter Type Description
tid string K-Pay internal transaction ID
refid string Your payment reference
momtransactionid string Transaction ID from financial institution
payaccount string Account/mobile number used for payment
statusid string 01 = Success, 02 = Failed
statusdesc string Description of transaction status
Your callback endpoint must return a specific response format to acknowledge receipt.
Callback Handler Examples
<?php
// callback.php - Your webhook endpoint
$input = file_get_contents('php://input');
$data = json_decode($input, true);

// Extract payment data
$tid = $data['tid'] ?? '';
$refid = $data['refid'] ?? '';
$statusid = $data['statusid'] ?? '';
$statusdesc = $data['statusdesc'] ?? '';
$momtransactionid = $data['momtransactionid'] ?? '';
$payaccount = $data['payaccount'] ?? '';

// Log the callback for debugging
error_log("K-Pay Callback: " . json_encode($data));

// Process based on status
if ($statusid === '01') {
    // Payment successful - update your database
    // $db->query("UPDATE orders SET status='paid', 
    //             transaction_id='$momtransactionid' 
    //             WHERE reference='$refid'");
} else {
    // Payment failed
    // $db->query("UPDATE orders SET status='failed' 
    //             WHERE reference='$refid'");
}

// IMPORTANT: Return required response
header('Content-Type: application/json');
echo json_encode([
    'tid' => $tid,
    'refid' => $refid,
    'reply' => 'OK'
]);
?>
// Express.js callback handler
const express = require('express');
const app = express();

app.use(express.json());

app.post('/callback', async (req, res) => {
    const { tid, refid, statusid, statusdesc, momtransactionid } = req.body;
    
    console.log('K-Pay Callback received:', req.body);
    
    try {
        if (statusid === '01') {
            // Payment successful
            // await Order.updateOne({ reference: refid }, { status: 'paid' });
            console.log('Payment successful for:', refid);
        } else {
            // Payment failed
            console.log('Payment failed for:', refid, statusdesc);
        }
        
        // IMPORTANT: Return required response
        res.json({
            tid: tid,
            refid: refid,
            reply: 'OK'
        });
    } catch (error) {
        console.error('Callback error:', error);
        res.status(500).json({ error: 'Processing failed' });
    }
});

app.listen(3000);
// Spring Boot Controller
@RestController
public class KPayCallbackController {
    
    @PostMapping("/callback")
    public ResponseEntity<Map<String, String>> handleCallback(
            @RequestBody KPayCallback callback) {
        
        String tid = callback.getTid();
        String refid = callback.getRefid();
        String statusid = callback.getStatusid();
        
        if ("01".equals(statusid)) {
            // Payment successful
            // orderService.updateOrderStatus(refid, "PAID");
            System.out.println("Payment successful for: " + refid);
        } else {
            // Payment failed
            System.out.println("Payment failed for: " + refid);
        }
        
        // Return required response
        Map<String, String> response = new HashMap<>();
        response.put("tid", tid);
        response.put("refid", refid);
        response.put("reply", "OK");
        
        return ResponseEntity.ok(response);
    }
}
// For Flutter mobile apps, handle callbacks on your backend.
// After redirect, verify payment status:

class PaymentVerificationService {
  Future<bool> verifyPayment(String refId) async {
    // After user returns from payment, verify the status
    final result = await kPayService.checkPaymentStatus(refId);
    
    if (result['success'] == true && 
        result['data']['statusid'] == '01') {
      // Payment confirmed
      await _updateLocalOrderStatus(refId, 'paid');
      return true;
    }
    return false;
  }
  
  Future<void> _updateLocalOrderStatus(String refId, String status) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('order_$refId', status);
  }
}
// ASP.NET Core Controller
[ApiController]
[Route("[controller]")]
public class CallbackController : ControllerBase
{
    [HttpPost]
    public IActionResult HandleCallback([FromBody] KPayCallback callback)
    {
        if (callback.StatusId == "01")
        {
            // Payment successful
            // _orderService.UpdateOrderStatus(callback.RefId, "Paid");
            Console.WriteLine($"Payment successful for: {callback.RefId}");
        }
        else
        {
            // Payment failed
            Console.WriteLine($"Payment failed for: {callback.RefId}");
        }

        // Return required response
        return Ok(new
        {
            tid = callback.Tid,
            refid = callback.RefId,
            reply = "OK"
        });
    }
}

public class KPayCallback
{
    public string Tid { get; set; }
    public string RefId { get; set; }
    public string MomTransactionId { get; set; }
    public string StatusId { get; set; }
    public string StatusDesc { get; set; }
}
Expected Response from Your Server
{
    "tid": "A441489693051",
    "refid": "ORDER123456789",
    "reply": "OK"
}

Payment Methods

Method pmethod Value Description
MTN Mobile Money momo MTN MoMo Rwanda
Airtel Money momo Airtel Money Rwanda
Visa cc Visa Credit/Debit Cards
Mastercard cc Mastercard Credit/Debit Cards
American Express cc American Express Cards
SPENN spenn SPENN Wallet

Test Cards

Use these test card numbers in sandbox/test mode:

Card Type Card Number Expiry CVV
Visa 4111 1111 1111 1111 Any future date Any 3 digits
Mastercard 5555 4444 3333 1111 Any future date Any 3 digits
American Express 3782 822463 10005 Any future date Any 4 digits
Test Mobile Numbers
Provider Test Number Result
MTN 250783000001 Success
MTN 250783000002 Insufficient funds
Airtel 250733000001 Success

Transaction Limits

Payment Method Minimum Maximum
Mobile Money (MTN/Airtel) 100 RWF 5,000,000 RWF
Credit/Debit Cards 1,000 RWF 10,000,000 RWF
SPENN 100 RWF 1,000,000 RWF
Transaction Fee: 5% per successful transaction

Return Codes

Status Codes
statusid Description
01 Success - Transaction completed
02 Failed - Transaction failed
03 Pending - Awaiting confirmation
Error Codes
retcode Reply Description
0PENDINGPayment initiated, awaiting customer action
600INVALID_REQUESTMissing or invalid parameters
601INVALID_API_KEYAPI key not found or inactive
602INVALID_AUTHBasic auth credentials invalid
603IP_NOT_WHITELISTEDRequest from unauthorized IP
604DUPLICATE_REFIDReference ID already used
605AMOUNT_OUT_OF_RANGEAmount below minimum or above maximum
606TARGET_AUTHORIZATION_ERRORPayment provider rejected transaction
607INSUFFICIENT_FUNDSCustomer has insufficient balance
608TIMEOUTTransaction timed out
609CANCELLEDTransaction cancelled by customer

Need Help?

If you have questions or need assistance with your integration:

Email Support Get Started