curl --request POST \
--url https://api.palpluss.com/v1/payments/stk \
--header 'Authorization: Basic <encoded-value>' \
--header 'Content-Type: application/json' \
--data '
{
"amount": 1000,
"phone": "0712345678",
"accountReference": "INV-2024-001",
"transactionDesc": "Payment for invoice #2024-001",
"channelId": "your-payment-channel-id",
"callbackUrl": "https://yourserver.com/webhooks/mpesa"
}
'{
"success": true,
"data": {
"transactionId": "fa98a577-95ea-4a8f-8467-1fbe74f5d6f4",
"tenantId": "b6fbe75f-ce87-4d44-b318-6cfdb8b7de4d",
"channelId": null,
"type": "STK",
"status": "PENDING",
"amount": 1000,
"currency": "KES",
"phone": "254712345678",
"accountReference": "INV-2024-001",
"transactionDesc": "Payment for invoice #2024-001",
"providerRequestId": "29115-34620561-1",
"providerCheckoutId": "ws_CO_191220191020363925",
"transactionFee": 2.5,
"resultCode": "0",
"resultDescription": "Success. Request accepted for processing",
"createdAt": "2026-03-01T08:00:00.000Z",
"updatedAt": "2026-03-01T08:00:00.000Z"
},
"requestId": "c1b2a3d4-0000-0000-0000-000000000000"
}Initiate STK Push
Send an M-Pesa payment prompt to a customer’s phone.
curl --request POST \
--url https://api.palpluss.com/v1/payments/stk \
--header 'Authorization: Basic <encoded-value>' \
--header 'Content-Type: application/json' \
--data '
{
"amount": 1000,
"phone": "0712345678",
"accountReference": "INV-2024-001",
"transactionDesc": "Payment for invoice #2024-001",
"channelId": "your-payment-channel-id",
"callbackUrl": "https://yourserver.com/webhooks/mpesa"
}
'{
"success": true,
"data": {
"transactionId": "fa98a577-95ea-4a8f-8467-1fbe74f5d6f4",
"tenantId": "b6fbe75f-ce87-4d44-b318-6cfdb8b7de4d",
"channelId": null,
"type": "STK",
"status": "PENDING",
"amount": 1000,
"currency": "KES",
"phone": "254712345678",
"accountReference": "INV-2024-001",
"transactionDesc": "Payment for invoice #2024-001",
"providerRequestId": "29115-34620561-1",
"providerCheckoutId": "ws_CO_191220191020363925",
"transactionFee": 2.5,
"resultCode": "0",
"resultDescription": "Success. Request accepted for processing",
"createdAt": "2026-03-01T08:00:00.000Z",
"updatedAt": "2026-03-01T08:00:00.000Z"
},
"requestId": "c1b2a3d4-0000-0000-0000-000000000000"
}Key notes
- Phone numbers are accepted in
07XXXXXXXX,01XXXXXXXX,+254XXXXXXXXX, or254XXXXXXXXXformat and normalised internally. accountReferencemust be 12 characters or fewer — it appears on the customer’s M-Pesa statement.transactionDescmust be 13 characters or fewer — it appears on the customer’s PIN prompt.- Save the returned
transactionId— use it to poll status or match incoming webhooks. PENDINGmeans the STK prompt was sent. The final result arrives via yourcallbackUrl.transactionFeein the response shows the service wallet amount deducted for this request. It is0when no pricing rule is configured.- A payment channel is required. If you omit
channelId, your account’s default channel is used. If your account has no channels configured, the request returns400 NO_PAYMENT_CHANNELS. If channels exist but none is set as default, the request returns400 NO_DEFAULT_CHANNEL.
Channel requirement
Every STK Push must be routed through a payment channel on your account. The channel determines which M-Pesa shortcode (Paybill or Till) the customer pays to.| Scenario | Behaviour |
|---|---|
channelId provided and found | Payment routed to that channel’s shortcode |
channelId omitted, default channel exists | Payment routed to the default channel’s shortcode |
channelId omitted, no default channel | 400 NO_DEFAULT_CHANNEL |
channelId omitted, account has no channels at all | 400 NO_PAYMENT_CHANNELS |
channelId provided but not found on account | 400 CHANNEL_NOT_FOUND |
channelId always route correctly.Error codes
| Code | HTTP | What it means | What to do |
|---|---|---|---|
NO_PAYMENT_CHANNELS | 400 | Account has no payment channels | Create a payment channel in the console first. |
NO_DEFAULT_CHANNEL | 400 | Channels exist but none is set as default | Set a default channel in the console, or pass channelId explicitly. |
CHANNEL_NOT_FOUND | 400 | Provided channelId not found on account | Verify the channel ID in the console. |
INVALID_PHONE | 400 | Phone number format not recognised | Use 07XXXXXXXX, 01XXXXXXXX, or 254XXXXXXXXX. |
INSUFFICIENT_SERVICE_BALANCE | 402 | Service wallet too low to cover the fee | Top up the service wallet and retry. |
STK_TEMP_BANNED | 429 | STK temporarily blocked due to low success rate | Wait details.retryAfterSeconds before retrying. |
Using a credential profile (credential_id)
The optional credential_id field lets you route the STK Push through your own M-Pesa Daraja credentials (BYOC — Bring Your Own Credentials) instead of the platform default.
When credential_id is provided:
- Daraja OAuth and the STK Push initiation use that profile’s
consumerKey,consumerSecret,passkey, andshortcode. - With a channel (
channelId) — the channel type determines the transaction type (PAYBILL→CustomerPayBillOnline,TILL→CustomerBuyGoodsOnline). The channel’s shortcode is used asPartyB. - Without a channel — the credential profile’s
transactionType(set when creating the profile) determines the transaction type. The profile’s shortcode is used asPartyB. Falls back to the platformDARAJA_TRANSACTION_TYPEsetting if the profile has notransactionTypeset.
MERCHANT_BYOC profile and copy its UUID to use here.Response fields
| Field | Type | Description |
|---|---|---|
transactionId | string | UUID of the created transaction |
type | string | Always "STK" |
status | string | Always "PENDING" on initiation |
amount | number | Payment amount in KES |
currency | string | Always "KES" |
phone | string | Normalised phone number (254XXXXXXXXX) |
accountReference | string | Reference shown on customer’s M-Pesa statement |
transactionDesc | string | Description shown on customer’s PIN prompt |
transactionFee | number | Service wallet fee charged for this request. 0 if no fee applies. |
providerRequestId | string | null | Safaricom MerchantRequestID |
providerCheckoutId | string | null | Safaricom CheckoutRequestID |
resultCode | string | null | Provider result code ("0" = accepted) |
resultDescription | string | null | Provider result message |
createdAt | string | ISO 8601 timestamp |
updatedAt | string | ISO 8601 timestamp |
STK abuse protection
If your API key initiates too many STK Pushes with a low success rate in a short window, further requests return429 STK_TEMP_BANNED. The ban lasts 1 hour. Check details.retryAfterSeconds for the exact wait time.Authorizations
Paste your PalPluss API key in the username field and leave the password field empty — it is ignored.
Get your key from console → Settings → API Keys (starts with pk_live_ or pk_test_).
In your own code, send the key directly:
Authorization: Basic YOUR_API_KEY
Body
Amount to charge in KES.
x >= 11000
Customer phone number. Accepted formats: 0712345678, 254712345678,
+254712345678. Normalised to 254XXXXXXXXX internally.
"0712345678"
Your reference — invoice number, order ID, etc.
12"INV-2024-001"
Short description shown on the customer's PIN prompt.
13"Payment"
HTTPS URL where PalPluss will POST the transaction result after the customer confirms or cancels payment.
"https://yourserver.com/webhooks/mpesa"
Optional. Route the STK Push through a specific payment channel (shortcode). If omitted, the default channel is used.
Optional. Use a specific MERCHANT_BYOC credential profile for this request.
When provided, Daraja OAuth and STK initiation use that profile's credentials
(consumerKey, consumerSecret, passkey, shortcode).
Transaction type (commandId) resolution when credential_id is set:
- Channel present → channel type drives it (
PAYBILL= CustomerPayBillOnline,TILL= CustomerBuyGoodsOnline) - No channel → profile's
transactionTypeis used when set; falls back to platform default
PartyB resolution when no channel is configured:
the credential profile's shortcode is used instead of the platform default shortcode.
Response
STK Push accepted. Transaction is in PENDING state.