Integrate your loan app
Your backend calls this deployment over HTTPS — no Paystack secret in the mobile app. Collect repayments with Paystack Checkout, pay out to M-Pesa with the disburse API, and confirm everything with verify + webhooks.
GET /api/payments as the route index, and the flows below (initialize → verify; disburse → optional approve; optional transfer status). If Bearer auth is on, they give you a key. You do not configure server env vars — skip the Operators section unless you run this deployment. Deep reference: docs/LOAN-APP-PAYMENTS-INTEGRATION.md.docs/LOAN-APP-PAYMENTS-INTEGRATION.md in the repository.Machine catalog:
GET /api/payments — JSON list of routes, bodies, and env vars.Try flows in the browser:
/payments/test (Checkout + disburse demos).Authentication & security
- Paystack —
PAYSTACK_SECRET_KEYis only on this API host. These routes call Paystack for you; you never send the Paystack secret in headers. - Optional Bearer — If
PAYMENTS_REQUIRE_BEARER=trueandPAYMENTS_API_KEYSis set, sendAuthorization: Bearer <key>on all/api/payments/*routes below (except Paystack’s own webhook, which usesx-paystack-signature). - Webhooks — Verify HMAC on the raw body per Paystack docs; invalid signature → our handler returns 400.
Flow: borrower repays (collect KES)
POST /api/payments/initializewithemail,amount_kes(major KES),callback_url, optionalmetadata(loan_id, etc.).- Open
authorization_urlin the user's browser (full redirect or WebView). - User returns to your
callback_urlwithreference(ortrxref) in the query string. - Your server calls
GET /api/payments/verify?reference=…and/or handlescharge.successon the webhook (coordinate with the API owner for ledger updates or event forwarding). - Persist
referenceidempotently — Paystack may retry webhooks.
When repayment "worked"
Verify JSON has status: "success" and a paid_at / amount in line with your order. See Paystack field notes in your verify response.
Flow: disburse to M-Pesa (payout)
mode: "automatic". Amounts ≥ KES 2000 → mode: "pending_approval" + approval_token, then POST /api/payments/disburse/approve with the OTP value the API owner documents for your integration.POST /api/payments/disbursewithamount_kes,recipient_phone(0711… or 254…),recipient_name, optionalreason.- If automatic: read
settlement+transfer.status.pendingis often normal — useGET /api/payments/transferor transfer webhooks for the final state. - If pending approval: after your own checks, call approve with the token and OTP.
Balance: insufficient Paystack KES → 400 with a clear message before a transfer is queued.
Test line: 0711164069 / 254711164069
HTTP status & JSON errors
Successful business operations return 200 with a JSON body (shape depends on route). Failures use { "message": "…" } unless noted.
| HTTP | Typical cause |
|---|---|
| 200 | Success — parse route-specific fields. |
| 400 | Validation, insufficient Paystack balance, invalid approval token/OTP, bad query (e.g. missing reference on verify). |
| 401 | Bearer required (PAYMENTS_REQUIRE_BEARER=true) but missing/wrong Authorization. |
| 502 | Paystack rejected the call or upstream error — read message (e.g. invalid key, Paystack transfer error). |
Disburse success shapes
| Field | Meaning |
|---|---|
mode | automatic | pending_approval | approved (after approve endpoint). |
settlement | processing | success | otp_required | failed | unknown — human-oriented read of Paystack transfer state. |
message | Explains especially when Paystack still shows pending. |
transfer | id, reference, status (raw Paystack), optional transfer_code. |
Endpoint quick reference
| Method & path | Purpose |
|---|---|
GET /api/payments | JSON catalog: all routes, env vars, webhook path. |
POST /api/payments/initialize | Start Checkout — returns authorization_url + reference. |
GET /api/payments/verify | Query reference — confirm a charge after redirect or as backfill. |
POST /api/payments/disburse | Payout to M-Pesa (auto or pending_approval). |
POST /api/payments/disburse/approve | Complete large transfer with approval_token + otp. |
GET /api/payments/transfer | Query reference or transfer_code — status + confirmation_message (SMS-ready). |
# Initialize (repayment)
curl -sS -X POST 'https://pewang.company/api/payments/initialize' \
-H 'Content-Type: application/json' \
-d '{"email":"borrower@example.com","amount_kes":500,"callback_url":"https://your-app.com/pay/return","metadata":{"loan_id":"LN-1"}}'
# Verify
curl -sS 'https://pewang.company/api/payments/verify?reference=PAYSTACK_REF'
# Disburse
curl -sS -X POST 'https://pewang.company/api/payments/disburse' \
-H 'Content-Type: application/json' \
-d '{"amount_kes":200,"recipient_phone":"0711164069","recipient_name":"Jane","reason":"Loan payout"}'Paystack → your deployment
Register this URL in the Paystack dashboard (live & test):
https://payment.pewang.company/api/paystack/webhookHandled for you today
transfer.success,transfer.failed,transfer.reversed— response JSON includesconfirmation_message. Optional server-to-server POST toPAYMENTS_TRANSFER_NOTIFY_URL(see env table below).
You should implement
charge.success(and failures) to mark loans paid — ask the API owner to forward events to your service or to extend the webhook handler on this host for your ledger.
Deployment operators — environment
Integrators can skip this. The team that hosts this project sets variables on the deployment (e.g. Vercel). After changes, they redeploy. CLI: docs/VERCEL-CLI.md.
| Variable | Role |
|---|---|
PAYSTACK_SECRET_KEY | Required — server → Paystack. |
PAYSTACK_PUBLIC_KEY | Optional — client Checkout if you build your own UI. |
PAYSTACK_WEBHOOK_SECRET | Optional — defaults to secret key for webhook HMAC. |
PAYMENT_APPROVAL_OTP | Required for /disburse/approve when amount ≥ 2000 KES. |
PAYMENTS_REQUIRE_BEARER / PAYMENTS_API_KEYS | Optional extra gate on payment HTTP routes. |
PAYSTACK_KES_MOBILE_BANK_CODE | Optional — default MPESA for M-Pesa recipients. |
PAYMENTS_TRANSFER_NOTIFY_URL | Optional — POST JSON (incl. confirmation_message) after transfer webhooks. |
PAYMENTS_TRANSFER_NOTIFY_SECRET | Optional — sent as X-Pewang-Notify-Secret to your notify URL. |
Repository documentation
- Loan integration (this spec, long-form):
docs/LOAN-APP-PAYMENTS-INTEGRATION.md - Curl recipes & notes:
docs/PAYMENTS-API.md - Vercel CLI & env:
docs/VERCEL-CLI.md