Connect your platform to Lipachap
Use this checklist when wiring a new backend — marketplace, savings group, billing system, or internal ops tool.
Architecture
Your platform Lipachap Mobile network
───────────── ───────── ──────────────
POST /pay ──────────────▶ POST /v1/walletcashin/process ──▶ STK / USSD push
Webhook ◀──────────────── POST your-webhook-url ◀── provider callback
GET status ──────────────▶ POST /v1/checkout/status
Step 1 — Register and obtain keys
- Create a merchant at lipachap.com/signup.
- Copy the TEST key pair from the dashboard (Settings → API keys).
- Request LIVE approval when you are ready for production traffic.
Step 2 — Configure environment variables
Never embed secrets in mobile apps or frontends. Your server should hold:
| Variable | Example |
|---|---|
LIPACHAP_BASE_URL | https://sandbox.api.lipachap.com |
LIPACHAP_API_KEY | Merchant API key |
LIPACHAP_API_SECRET | Merchant API secret |
LIPACHAP_WEBHOOK_SECRET | Per-endpoint signing secret |
Step 3 — Implement the payment client
Build a small SDK wrapper in your stack that:
- Serializes JSON once and reuses the same string for signing and the HTTP body
- Signs with
sha256(secret + path + rawBody) - Sends
Content-Type: application/jsonand optionalX-Request-Idfor tracing - Stores the Lipachap
transidyou generate (must be unique per merchant)
const crypto = require('crypto');
function lipachapAuthHeader(apiKey, apiSecret, requestPath, rawBody = '') {
const digest = crypto
.createHash('sha256')
.update(apiSecret + requestPath + rawBody)
.digest('hex');
const token = Buffer.from(`${apiKey}:${digest}`).toString('base64');
return `GATEWAY ${token}`;
}
// Example
const path = '/v1/walletcashin/process';
const body = JSON.stringify({
transid: 'TXN-001',
amount: 5000,
msisdn: '255712345678',
utilityref: 'ORDER-123',
utilitycode: 'VMCASHIN',
vendor: 'YOUR_VENDOR',
pin: '0000',
});
const authorization = lipachapAuthHeader(process.env.LIPACHAP_API_KEY, process.env.LIPACHAP_API_SECRET, path, body);import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import org.apache.commons.codec.digest.DigestUtils;
public final class LipachapAuth {
public static String authorizationHeader(String apiKey, String apiSecret, String path, String rawBody) {
String signed = apiSecret + path + (rawBody == null ? "" : rawBody);
String digest = DigestUtils.sha256Hex(signed);
String credentials = apiKey + ":" + digest;
return "GATEWAY " + Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8));
}
}
// Usage
String path = "/v1/walletcashin/process";
String body = "{\"transid\":\"TXN-001\",\"amount\":5000,\"msisdn\":\"255712345678\"}";
String auth = LipachapAuth.authorizationHeader(apiKey, apiSecret, path, body);<?php
function lipachap_auth_header(string $apiKey, string $apiSecret, string $path, string $rawBody = ''): string {
$digest = hash('sha256', $apiSecret . $path . $rawBody);
$token = base64_encode($apiKey . ':' . $digest);
return 'GATEWAY ' . $token;
}
$path = '/v1/walletcashin/process';
$body = json_encode([
'transid' => 'TXN-001',
'amount' => 5000,
'msisdn' => '255712345678',
'utilityref' => 'ORDER-123',
], JSON_UNESCAPED_SLASHES);
$authorization = lipachap_auth_header(getenv('LIPACHAP_API_KEY'), getenv('LIPACHAP_API_SECRET'), $path, $body);import base64
import hashlib
import json
import os
def lipachap_auth_header(api_key: str, api_secret: str, path: str, raw_body: str = "") -> str:
digest = hashlib.sha256((api_secret + path + raw_body).encode("utf-8")).hexdigest()
token = base64.b64encode(f"{api_key}:{digest}".encode("utf-8")).decode("ascii")
return f"GATEWAY {token}"
path = "/v1/walletcashin/process"
body = json.dumps({
"transid": "TXN-001",
"amount": 5000,
"msisdn": "255712345678",
"utilityref": "ORDER-123",
"utilitycode": "VMCASHIN",
"vendor": "YOUR_VENDOR",
"pin": "0000",
}, separators=(",", ":"))
authorization = lipachap_auth_header(
os.environ["LIPACHAP_API_KEY"],
os.environ["LIPACHAP_API_SECRET"],
path,
body,
)package lipachap
import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
)
func AuthHeader(apiKey, apiSecret, path, rawBody string) string {
sum := sha256.Sum256([]byte(apiSecret + path + rawBody))
digest := hex.EncodeToString(sum[:])
token := base64.StdEncoding.EncodeToString([]byte(apiKey + ":" + digest))
return "GATEWAY " + token
}
// Usage in a handler:
// path := "/v1/walletcashin/process"
// body := `{"transid":"TXN-001","amount":5000,"msisdn":"255712345678"}`
// req.Header.Set("Authorization", AuthHeader(apiKey, apiSecret, path, body))Step 4 — Collections vs disbursements
| Use case | Endpoint | Type |
|---|---|---|
| Customer pays you (contribution, checkout, top-up) | POST /v1/walletcashin/process | CASHIN |
| You pay a customer (refund, loan payout, withdrawal) | POST /v1/gateway/cashin | CASHOUT |
| Reconcile a pending payment | POST /v1/checkout/status | Query |
Required body fields for payments: transid, amount, msisdn. MSISDN may be E.164 (2557…) or national format; optional countryCode helps routing.
Step 5 — Expose a webhook endpoint
- Add
POST /webhooks/lipachap(or similar) on your backend. - Register the URL in the Lipachap dashboard with a signing secret.
- Verify
X-Gateway-Signaturebefore updating order/contribution state. - Respond with HTTP 200 quickly; process asynchronously if needed.
- Make handlers idempotent — Lipachap retries failed deliveries.
const crypto = require('crypto');
function verifyWebhook(rawBody, timestamp, signature, secret, maxSkewSec = 300) {
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - Number(timestamp)) > maxSkewSec) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const received = signature.replace(/^sha256=/, '');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}
// Express example
app.post('/webhooks/lipachap', express.raw({ type: 'application/json' }), (req, res) => {
const rawBody = req.body.toString('utf8');
const ok = verifyWebhook(
rawBody,
req.get('X-Gateway-Timestamp'),
req.get('X-Gateway-Signature'),
process.env.LIPACHAP_WEBHOOK_SECRET,
);
if (!ok) return res.sendStatus(401);
const event = JSON.parse(rawBody);
// event.transid, event.status, event.amount, ...
res.sendStatus(200);
});import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
public boolean verifyWebhook(String rawBody, String timestamp, String signature, String secret) {
String payload = timestamp + "." + rawBody;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String expected = HexFormat.of().formatHex(mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)));
String received = signature.replace("sha256=", "");
return MessageDigest.isEqual(expected.getBytes(), received.getBytes());
}function verify_lipachap_webhook(string $rawBody, string $timestamp, string $signature, string $secret): bool {
$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
$received = preg_replace('/^sha256=/', '', $signature);
return hash_equals($expected, $received);
}
$rawBody = file_get_contents('php://input');
$ok = verify_lipachap_webhook(
$rawBody,
$_SERVER['HTTP_X_GATEWAY_TIMESTAMP'] ?? '',
$_SERVER['HTTP_X_GATEWAY_SIGNATURE'] ?? '',
getenv('LIPACHAP_WEBHOOK_SECRET'),
);import hmac
import hashlib
import time
def verify_webhook(raw_body: bytes, timestamp: str, signature: str, secret: str, max_skew: int = 300) -> bool:
if abs(int(time.time()) - int(timestamp)) > max_skew:
return False
expected = hmac.new(secret.encode(), f"{timestamp}.{raw_body.decode()}".encode(), hashlib.sha256).hexdigest()
received = signature.removeprefix("sha256=")
return hmac.compare_digest(expected, received)func VerifyWebhook(rawBody []byte, timestamp, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(timestamp + "." + string(rawBody)))
expected := hex.EncodeToString(mac.Sum(nil))
received := strings.TrimPrefix(signature, "sha256=")
return hmac.Equal([]byte(expected), []byte(received))
}Step 6 — Go live
- Switch
LIPACHAP_BASE_URLtohttps://api.lipachap.com - Replace TEST keys with LIVE keys
- Point webhooks to your production HTTPS URL
- Run a small real-money test before full rollout
Platform examples
- Vikoba / microfinance — CASHIN for member contributions and loan repayments; CASHOUT for loan disbursements; pass fee fields on withdrawals.
- E-commerce — CASHIN at checkout; webhook marks order paid; status API for polling fallback.
- Marketplace — separate utilityref per vendor payout batch; ledger Lipachap reference on your side.