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

  1. Create a merchant at lipachap.com/signup.
  2. Copy the TEST key pair from the dashboard (Settings → API keys).
  3. 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:

VariableExample
LIPACHAP_BASE_URLhttps://sandbox.api.lipachap.com
LIPACHAP_API_KEYMerchant API key
LIPACHAP_API_SECRETMerchant API secret
LIPACHAP_WEBHOOK_SECRETPer-endpoint signing secret

Step 3 — Implement the payment client

Build a small SDK wrapper in your stack that:

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 caseEndpointType
Customer pays you (contribution, checkout, top-up)POST /v1/walletcashin/processCASHIN
You pay a customer (refund, loan payout, withdrawal)POST /v1/gateway/cashinCASHOUT
Reconcile a pending paymentPOST /v1/checkout/statusQuery

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

  1. Add POST /webhooks/lipachap (or similar) on your backend.
  2. Register the URL in the Lipachap dashboard with a signing secret.
  3. Verify X-Gateway-Signature before updating order/contribution state.
  4. Respond with HTTP 200 quickly; process asynchronously if needed.
  5. 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

Platform examples