Complete guide to integrating K-Pay payment gateway into your application.
The K-Pay API provides programmatic access to accept payments via Mobile Money (MTN, Airtel), Visa, Mastercard, American Express, SmartCash, and SPENN.
https://pay.esicia.com/
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 |
<?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"));
Initiate a payment by making a POST request with the following parameters:
POST https://pay.esicia.com/
| 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 |
<?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";
}
{
"reply": "PENDING",
"url": "https://pay.esicia.com/checkout/A12343983489",
"success": 1,
"authkey": "m43snbf9oivnmersqh6mn1lbh5",
"tid": "E6974831594723691",
"refid": "ORDER123456789",
"retcode": 0
}
{
"reply": "TARGET_AUTHORIZATION_ERROR",
"url": "",
"success": 0,
"authkey": "m43snbf9oivnmersqh6mn1lbh5",
"tid": "E6974831594723691",
"refid": "ORDER123456789",
"retcode": 606
}
Check the status of a payment using the reference ID.
POST https://pay.esicia.com/
| Parameter | Required | Type | Description |
|---|---|---|---|
action |
Required | string | Must be "checkstatus" |
refid |
Required | string | Payment reference from your system |
<?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;
}
{
"tid": "A441489693051",
"refid": "ORDER123456789",
"momtransactionid": "616730887",
"statusid": "01",
"statusdesc": "Successfully processed transaction."
}
{
"tid": "E6974831594723691",
"refid": "ORDER123456789",
"momtransactionid": "12943154",
"statusdesc": "TARGET_AUTHORIZATION_ERROR",
"statusmsg": "Not enough funds",
"statusid": "02"
}
K-Pay will send a POST request to your returl when the payment status changes.
| 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 |
<?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; }
}
{
"tid": "A441489693051",
"refid": "ORDER123456789",
"reply": "OK"
}
| Method | pmethod Value |
Description |
|---|---|---|
momo |
MTN MoMo Rwanda | |
momo |
Airtel Money Rwanda | |
cc |
Visa Credit/Debit Cards | |
cc |
Mastercard Credit/Debit Cards | |
cc |
American Express Cards | |
| SPENN | spenn |
SPENN Wallet |
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 |
| Provider | Test Number | Result |
|---|---|---|
| MTN | 250783000001 |
Success |
| MTN | 250783000002 |
Insufficient funds |
| Airtel | 250733000001 |
Success |
| 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 |
statusid |
Description |
|---|---|
01 |
Success - Transaction completed |
02 |
Failed - Transaction failed |
03 |
Pending - Awaiting confirmation |
retcode |
Reply | Description |
|---|---|---|
| 0 | PENDING | Payment initiated, awaiting customer action |
| 600 | INVALID_REQUEST | Missing or invalid parameters |
| 601 | INVALID_API_KEY | API key not found or inactive |
| 602 | INVALID_AUTH | Basic auth credentials invalid |
| 603 | IP_NOT_WHITELISTED | Request from unauthorized IP |
| 604 | DUPLICATE_REFID | Reference ID already used |
| 605 | AMOUNT_OUT_OF_RANGE | Amount below minimum or above maximum |
| 606 | TARGET_AUTHORIZATION_ERROR | Payment provider rejected transaction |
| 607 | INSUFFICIENT_FUNDS | Customer has insufficient balance |
| 608 | TIMEOUT | Transaction timed out |
| 609 | CANCELLED | Transaction cancelled by customer |
If you have questions or need assistance with your integration:
Email Support Get Started