Make sure you are using the right credentials and base URL for your environment. Sandbox credentials (
https://sandbox.kulmipay.com) do not work against the live API (https://app.kulmipay.com), and vice versa. This is one of the most common causes of unexpected errors when moving from testing to production.Authentication errors: Invalid credentials or expired token
Authentication errors: Invalid credentials or expired token
Error messages:
Invalid credentials, Expired token, Authentication failedKulmi Pay uses OAuth 2.0 password grant. Check the following:- Confirm you are passing
"grant_type": "password"in your token request body. - Verify your API caller
usernameandpasswordare correct and yourCLIENT-IDandCLIENT-SECRETare passed as HTTP Basic auth credentials. - Verify credentials belong to the right environment (sandbox vs. live).
- Tokens expire after a period of inactivity. If you receive an expired token error, request a new token from
POST /api/token/before retrying. - Do not hardcode tokens. Implement token caching and automatic refresh in your integration.
Token request
Invalid provider
Invalid provider
Error message:
invalid provider for send money. Allowed values are ...The provider field in your disbursement request must exactly match one of the allowed provider codes. Common valid values include:MPESA-B2C— M-Pesa business-to-customer transfersMPESA-B2B— M-Pesa business-to-business transfers (PayBill/Till)PESALINK— Bank transfers via PesaLink
GET /api/v1/send-money/ or reviewing the error message, which returns all allowed values. Provider codes are case-sensitive — mpesa-b2c will fail; use MPESA-B2C.Duplicate transaction: idempotency_key already used
Duplicate transaction: idempotency_key already used
Error message:
duplicate transaction detected. A transaction with similar idempotency_key already existsEvery transaction in a disbursement batch can include an optional idempotency_key to prevent accidental duplicate payments. If you submit a transaction with a key you have already used, Kulmi Pay rejects the entire request.- Use a unique value per transaction — a UUID or a value derived from your internal order or payment ID works well.
- If you are retrying a failed request, generate a new
idempotency_keyunless you are certain the original transaction did not process. - If you intentionally want to re-send the same payment, omit the
idempotency_keyfield.
Insufficient balance
Insufficient balance
Error message:
Insufficient balance or similar wallet balance errorYour wallet does not have enough funds to cover the total disbursement amount plus any applicable fees.- Check your current wallet balance before initiating a bulk disbursement. Retrieve balances from
GET /api/v1/wallets/. - If you have multiple wallets in the same currency, confirm you are disbursing from the correct wallet by specifying
wallet_idin your request. - Top up your wallet via the dashboard before retrying the disbursement.
Amount below minimum
Amount below minimum
Error message:
Review each transaction in your batch and remove or increase any amounts that fall below the provider’s minimum before resubmitting.
Amount must be equal or more than KES. 10 for M-Pesa payments or Amount must be equal or more than KES. 100Kulmi Pay enforces minimum transaction amounts for certain providers:| Provider | Minimum amount |
|---|---|
MPESA-B2C | KES 10 |
PESALINK | KES 100 |
Webhook not receiving events
Webhook not receiving events
If your endpoint is not receiving webhook events, work through these checks:
- Verify your endpoint is publicly reachable. Kulmi Pay cannot deliver to
localhostor endpoints behind a firewall. Use a tool like ngrok for local development. - Confirm the endpoint uses HTTPS with a valid, trusted TLS certificate. Self-signed certificates are not accepted.
- Check your webhook failure count in Settings → Webhooks. If the count is high and
is_activeisfalse, your webhook has been automatically disabled. Fix the underlying issue, then re-enable it in the dashboard. - Confirm you have subscribed to the right event flags. If you are not seeing collection events, check that
collection_eventis enabled on your webhook. - Check the Events log at
GET /api/v1/webhooks/events/to see if events were created with aFAILEDstatus. If they exist, replay them once your endpoint is fixed.
Payment file not in correct state
Payment file not in correct state
Error message:
Payment file must be in PREVIEW-AND-APPROVE state to perform this actionYou can only approve a disbursement payment file when it is in PREVIEW-AND-APPROVE state. This state is reached after a file passes initial validation.- Do not attempt to approve a file that is still
PENDINGor has already beenPROCESSEDorCANCELED. - Check the file’s current state by calling
POST /api/v1/send-money/status/with yourtracking_idbefore calling the approve endpoint. - If you submitted the disbursement with
"requires_approval": "NO", the file is approved automatically and cannot be approved again.
Invalid bank_code for PesaLink transfers
Invalid bank_code for PesaLink transfers
Error message: Use the exact
Field bank_code is required for bank transactions or an invalid bank code errorPesaLink bank transfers require a valid bank_code for every transaction. If you omit the field or use an incorrect code, the transaction is rejected.Retrieve the full list of valid bank codes for Kenya:Example response
bank_code string from this list in your transaction payload.can_transact is false: account not verified
can_transact is false: account not verified
Error message:
can_transact is false or a verification-related errorYour business account has not completed identity verification (KYC). Kulmi Pay requires KYC before allowing live transactions.- Log in to your dashboard at app.kulmipay.com and navigate to Settings → Business Verification.
- Complete all required verification steps, including uploading business registration documents and identity documents for the account owner.
- Verification is typically reviewed within one business day. You will receive an email when your account is approved.
- In the sandbox environment,
can_transactis always enabled so you can test without completing verification.
