ArchPay API Reference

ArchPay gives you a simple REST API to trigger M-Pesa STK Push payments, verify payment status, and receive real-time webhook callbacks. One API key. No SDK required.

Base URL
https://pay.archietech.app/api/v1
Content-Type โ€” All requests must include Content-Type: application/json
Auth header โ€” All requests must include x-api-key: YOUR_API_KEY

API Key Authentication

Every request to the API must include your API key in the x-api-key header. You receive your key immediately on account creation. Keys start with apk_.

Required headers
x-api-key: apk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Content-Type: application/json
Keep your key secret. Do not expose it in frontend JavaScript or public repositories. Rotate it immediately from your dashboard if compromised.

Alternative: Public Key + Secret Key

For client-side or split-credential use, you can authenticate with your public key and secret key as separate headers instead of the combined API key.

Split credential headers
x-public-key: pk_xxxxxxxxxxxxxxxxxxxx x-secret-key: sk_xxxxxxxxxxxxxxxxxxxx Content-Type: application/json

Base URL & Versioning

The current API version is v1. All endpoints are prefixed with /api/v1.

Base URL
https://pay.archietech.app/api/v1
Health Check
GET https://pay.archietech.app/health // Response { "status": "ok", "service": "ArchPay", "version": "2.0.0", "ts": "2025-01-01T12:00:00.000Z" }

Trigger STK Push

Sends an M-Pesa payment prompt to the customer's phone. The customer sees a PIN entry dialog. Consumes 1 credit per call.

POST /stkpush pay.archietech.app/api/v1/stkpush

Request Body

ParameterTypeDescription
phonerequiredstringSafaricom number in 254XXXXXXXXX format (12 digits, starts with 254)
amountrequiredintegerAmount in KES. Must be a whole number โ‰ฅ 1. Max: 300,000
accountReferenceoptionalstringReference shown on customer M-Pesa statement. Max 12 chars. Defaults to your business name.
descriptionoptionalstringTransaction description. Max 20 chars.
channelIdoptionalstringRoute payment through a specific channel (Paybill / Till / Bank). Get channel IDs from GET /channels. If omitted, uses platform default.
๐Ÿ’ก Sandbox mode โ€” When your account is in sandbox mode, STK pushes are simulated (no real M-Pesa calls). The response is identical but "isSandbox": true is set on the transaction. Use sandbox to test your integration without spending credits or triggering real payments.
Request โ€” cURL
curl -X POST https://pay.archietech.app/api/v1/stkpush \ -H "x-api-key: apk_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "phone": "254712345678", "amount": 1500, "accountReference": "INV-001", "description": "Order payment" }'
Success Response (200)
{ "success": true, "message": "STK push sent. Awaiting customer PIN.", "checkoutRequestId": "ws_CO_271020231234567890", "merchantRequestId": "29115-34620561-1", "channelId": "CHN123456", // null if default channel "creditsRemaining": 9 }
Error โ€” Insufficient Credits (402)
{ "success": false, "code": "INSUFFICIENT_CREDITS", "error": "Insufficient credits" }

List Channels

Returns all payment channels configured for your account (Paybill, Till, Banks). Use the channelId from this response in the /stkpush request to route payments through a specific channel.

GET /channels pay.archietech.app/api/v1/channels
Request
curl https://pay.archietech.app/api/v1/channels \ -H "x-api-key: apk_your_key_here"
Response (200)
{ "success": true, "channels": [ { "channelId": "CHN123456", // use this in /stkpush "name": "Shop Till", "type": "till", // paybill | till | bank_kcb | bank_equity | โ€ฆ "provider": "m-pesa", "active": true }, { "channelId": "CHN789012", "name": "Main Paybill", "type": "paybill", "provider": "m-pesa", "active": true } ] }

To route an STK push through a specific channel, include "channelId": "CHN123456" in your /stkpush body. Channels not owned by your account or inactive channels return CHANNEL_NOT_FOUND / CHANNEL_INACTIVE.

Verify Payment Status

Poll the status of a specific STK push transaction using its checkoutRequestId. Use this as a fallback โ€” webhooks are the recommended way to receive results.

POST /verify pay.archietech.app/api/v1/verify
ParameterTypeDescription
checkoutRequestIdrequiredstringThe checkoutRequestId returned by the /stkpush call
Request
curl -X POST https://pay.archietech.app/api/v1/verify \ -H "x-api-key: apk_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "checkoutRequestId": "ws_CO_271020231234567890" }'
Response (200)
{ "success": true, "status": "completed", "mpesaReceiptNumber": "RFP20XXXXXXX", "amount": 1500, "phone": "254712345678", "transactionDate": "20250101120000" }

List Transactions

Returns a paginated list of all transactions made with your API key, sorted by most recent first.

GET /transactions pay.archietech.app/api/v1/transactions
Query ParameterTypeDescription
limitoptionalintegerMax results to return. Default: 50, Max: 200
statusoptionalstringFilter by status: completed, pending, failed, cancelled, timeout
Request
curl https://pay.archietech.app/api/v1/transactions?limit=50 \ -H "x-api-key: apk_your_key_here"
Response (200)
{ "success": true, "transactions": [ { "_id": "64f...", "checkoutRequestId": "ws_CO_271020231234567890", "phone": "254712345678", "amount": 1500, "status": "completed", "mpesaReceiptNumber": "RFP20XXXXXXX", "accountReference": "INV-001", "createdAt": "2025-01-01T12:00:00.000Z" } ], "count": 1 }

Check Credit Balance

Returns the current credit balance for your account. Each successful STK push consumes 1 credit.

GET /balance pay.archietech.app/api/v1/balance
Request
curl https://pay.archietech.app/api/v1/balance \ -H "x-api-key: apk_your_key_here"
Response (200)
{ "success": true, "credits": 47, "businessName": "Acme Ltd" }

Webhook Payload

When a payment is confirmed or fails, ArchPay sends an HTTP POST to your configured webhook URL. This happens within seconds of M-Pesa processing the transaction.

Webhook POST body
{ "event": "payment.callback", "status": "completed", "checkoutRequestId": "ws_CO_271020231234567890", "mpesaReceiptNumber": "RFP20XXXXXXX", // null if not completed "amount": 1500, "phone": "254712345678", "accountReference": "INV-001", "timestamp": "2025-01-01T12:00:00.000Z" }
Your webhook endpoint must return a 2xx HTTP status within 10 seconds. ArchPay does not currently retry failed webhook deliveries โ€” ensure your endpoint is reliable.

Payment Statuses

Every transaction has one of these statuses. Only completed means money moved.

completed
Payment confirmed. M-Pesa receipt number present. Money received.
pending
STK push sent, awaiting customer PIN entry. Poll or wait for webhook.
failed
Payment failed. Could be wrong PIN, insufficient funds, or M-Pesa error.
cancelled
Customer dismissed or cancelled the STK prompt.
timeout
Customer did not respond to the STK prompt within the allowed window.

Configuring Your Webhook

Set your webhook URL from the Settings tab in your dashboard. It must be a publicly accessible HTTPS endpoint.

Express.js webhook handler
app.post('/webhooks/mpesa', (req, res) => { const { event, status, checkoutRequestId, mpesaReceiptNumber, amount, phone } = req.body; if (status === 'completed') { // Payment confirmed โ€” fulfill the order console.log(`Payment ${mpesaReceiptNumber}: KSH ${amount} from ${phone}`); } else if (status === 'failed' || status === 'cancelled') { // Payment did not go through console.log(`Payment ${checkoutRequestId} ${status}`); } res.status(200).json({ received: true }); // must return 2xx });

Error Handling

All errors return JSON with "success": false, an "error" message, and a machine-readable "code" string for programmatic handling.

Error response shape
{ "success": false, "code": "INSUFFICIENT_CREDITS", // machine-readable code "error": "Insufficient credits" // human-readable message }

Error Codes

CodeHTTPMeaning
MISSING_FIELDS400Required fields missing from request body
INVALID_PHONE400Phone number not in 254XXXXXXXXX format
INVALID_AMOUNT400Amount is not a positive integer
UNAUTHORIZED401Missing or invalid API key
ACCOUNT_DISABLED403Your account has been suspended
INSUFFICIENT_CREDITS402Account credits exhausted โ€” top up to continue
CHANNEL_NOT_FOUND404channelId does not exist or does not belong to your account
CHANNEL_INACTIVE403Channel exists but is currently disabled
B2C_DISABLED403B2C payments are not enabled on this account
SERVICE_UNAVAILABLE503Platform maintenance mode is active โ€” retry shortly
MPESA_ERROR502Safaricom API returned an error โ€” check details field

HTTP Status Codes

StatusMeaningCommon cause
400Bad RequestMissing required fields, invalid phone format, or non-integer amount
401UnauthorizedMissing or invalid x-api-key header
402Payment RequiredAccount has zero credits โ€” top up to continue
403ForbiddenAccount suspended, channel inactive, or B2C disabled
404Not FoundTransaction or channel ID not found
502Bad GatewayM-Pesa API error โ€” check the details field for Safaricom's error message
503Service UnavailablePlatform under maintenance โ€” retry after a moment

Quick Start

From zero to your first STK push in 3 steps.

Step 1 โ€” Get your API key
# Sign up at https://pay.archietech.app โ†’ Get Started # Your API key appears immediately on the dashboard (starts with apk_)
Step 2 โ€” Send a test STK push
curl -X POST https://pay.archietech.app/api/v1/stkpush \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone": "2547XXXXXXXX", "amount": 1 }'
Step 3 โ€” Set up your webhook
# In your dashboard โ†’ Settings โ†’ Webhook URL # Enter: https://yourapp.com/webhooks/mpesa # Then handle the POST in your server (see Webhook section above)

Node.js Integration

Node.js / fetch
const ARCHPAY_KEY = process.env.ARCHPAY_API_KEY; const BASE = 'https://pay.archietech.app/api/v1'; async function stkPush(phone, amount, reference) { const res = await fetch(`${BASE}/stkpush`, { method: 'POST', headers: { 'x-api-key': ARCHPAY_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ phone, amount, accountReference: reference }), }); return res.json(); } // Usage const result = await stkPush('254712345678', 500, 'ORDER-001'); console.log(result.checkoutRequestId); // save this to verify later

Python Integration

Python / requests
import requests, os ARCHPAY_KEY = os.environ['ARCHPAY_API_KEY'] BASE = 'https://pay.archietech.app/api/v1' HEADERS = {'x-api-key': ARCHPAY_KEY, 'Content-Type': 'application/json'} def stk_push(phone, amount, reference='Payment'): r = requests.post( f'{BASE}/stkpush', headers=HEADERS, json={'phone': phone, 'amount': amount, 'accountReference': reference} ) return r.json() # Usage result = stk_push('254712345678', 500, 'ORDER-001') print(result['checkoutRequestId']) # save for verification
Terms  ยท  Privacy  ยท  ยฉ 2026 ArchTech