Account Balance
Request account balances, receive asynchronous result notifications, and handle timeout callbacks from Safaricom's Account Balance API.
User Story
Who can use this service
As a finance or operations user, I want to query my organization's M-Pesa account balance so I can reconcile payments and spot problems quickly.
- Finance analyst: runs balance checks and reconciliation reports.
- Till manager / cashier: monitors till balances and requests top-ups when low.
- Backend system / automation: polls balances regularly and creates alerts or workflows on anomalies.
This guide documents the Account Balance flow: how to construct queries, expected acknowledgement responses, how to parse ResultURL callbacks (final balances) and QueueTimeOutURL notifications, and important validation/safety checks to implement on your side.
Parameters Definition
Parameter | Type | Description |
---|---|---|
Initiatorrequired str | String | Username used to initiate the request (API initiator identity). |
SecurityCredentialrequired str | String | Encrypted security credential (as required by Safaricom). |
CommandIDrequired str | String | Transaction type. Default: 'AccountBalance'. |
PartyArequired int | Integer | Organization shortcode, till number or MSISDN depending on IdentifierType. |
IdentifierTyperequired int | Integer | Type of PartyA. Allowed values: 1 (MSISDN), 2 (Till number), 4 (Short code). |
Remarksrequired str | String | Freeform comments for the transaction (must not exceed 100 characters). |
QueueTimeOutURLrequired str | String | Callback URL for timeout notifications (QueueTimeOutURL). |
ResultURLrequired str | String | Callback URL where final account balance result will be posted. |
Responses & Result Notifications
Parameter | Type | Description |
---|---|---|
OriginatorConversationID str | String | Unique identifier for the original request acknowledgement. |
ConversationID str | String | Unique transaction identifier returned by M-Pesa. |
ResponseCoderequired str|int | String/Integer | Acknowledgement status code. '0' indicates acceptance of the request. |
ResponseDescriptionrequired str | String | Human readable acknowledgement message. |
Result.ResultType / Result.ResultCode / Result.ResultDescrequired int|int|str | Integer/Integer/String | Result metadata posted to ResultURL. ResultType=0 => success; ResultCode=0 => success. |
Result.ResultParameter.ResultParameters list | Array | List of key/value result parameters. 'AccountBalance' key contains pipe- and ampersand-delimited balance blobs for accounts. |
ReferenceData.ReferenceItem object | Object | Reference item included in callbacks (e.g., original QueueTimeoutURL). |
Overview
Query lifecycle: send AccountBalance request → receive immediate ack → process asynchronous result or timeout.
- Use the high-level MpesaClient/BalanceService facade for token management, header injection and typed Pydantic models.
- Use the lower-level AccountBalance class with a TokenManager and HttpClient if you need full control over request construction, middleware and error handling.
The facade handles authentication and returns typed models (acknowledgement and parsed callbacks). Use it for concise integration in most applications.
Quick Setup
# Requires: mpesakit, fastapi (for webhook example)from mpesakit import MpesaClientfrom mpesakit.account_balance import ( AccountBalanceResultCallback, AccountBalanceTimeoutCallback, AccountBalanceIdentifierType,)from mpesakit.security.ip_whitelist import is_mpesa_ip_allowed
# ---- Client / Request (synchronous example) ----client = MpesaClient(consumer_key="YOUR_KEY", consumer_secret="YOUR_SECRET", environment="sandbox")
resp = client.balance.query( initiator="apiuser", security_credential="ENCRYPTED_SECURITY_CREDENTIAL", party_a=600000, identifier_type=AccountBalanceIdentifierType.SHORT_CODE, remarks="Balance inquiry", result_url="https://your.service/webhook/account-balance/result", queue_timeout_url="https://your.service/webhook/account-balance/timeout",)
if resp.is_successful(): print("Acknowledgement accepted:", resp.ConversationID or resp.OriginatorConversationID)else: print("Acknowledgement failed:", resp.ResponseDescription)
Webhook Handling (Result & Timeout)
# Example: FastAPI endpoints for Account Balance Result & Timeout callbacksfrom fastapi import FastAPI, Request, HTTPExceptionfrom mpesakit.account_balance import ( AccountBalanceResultCallback, AccountBalanceTimeoutCallback, AccountBalanceResultCallbackResponse, AccountBalanceTimeoutCallbackResponse,)from mpesakit.security.ip_whitelist import is_mpesa_ip_allowedimport logging
app = FastAPI()logger = logging.getLogger("mpesa.account_balance")
async def _caller_ip(request: Request) -> str: return (request.headers.get("x-forwarded-for") or request.client.host).split(",")[0].strip()
@app.post("/webhook/account-balance/result")async def account_balance_result(request: Request): payload = await request.json() caller_ip = await _caller_ip(request) if not is_mpesa_ip_allowed(caller_ip): raise HTTPException(status_code=403, detail="forbidden")
# Validate and parse incoming payload into project models try: callback = AccountBalanceResultCallback(**payload) except Exception as exc: logger.exception("Invalid AccountBalance result payload") return AccountBalanceResultCallbackResponse(ResultCode=1, ResultDesc=f"Invalid payload: {exc}")
response = callback.model_dump(mode="json") logger.info("AccountBalance result received: %s", response)
return AccountBalanceResultCallbackResponse() # defaults to ResultCode=0, ResultDesc="Result received and processed successfully."
@app.post("/webhook/account-balance/timeout")async def account_balance_timeout(request: Request): payload = await request.json() caller_ip = await _caller_ip(request) if not is_mpesa_ip_allowed(caller_ip): raise HTTPException(status_code=403, detail="forbidden")
try: callback = AccountBalanceTimeoutCallback(**payload) except Exception as exc: logger.exception("Invalid AccountBalance timeout payload") return AccountBalanceTimeoutCallbackResponse(ResultCode=1, ResultDesc=f"Invalid payload: {exc}")
response = callback.model_dump(mode="json") logger.warning("AccountBalance timeout received: %s", response)
# Quick typed acknowledgement to stop retries return AccountBalanceTimeoutCallbackResponse() # defaults to ResultCode=0, ResultDesc="Timeout notification received and processed successfully."
Important behaviors & validations
- IdentifierType must be one of the supported enum values (1=MSISDN, 2=Till number, 4=Short code). Invalid values should be rejected before sending requests.
- Remarks must not exceed 100 characters; the client/model will raise validation errors if this rule is violated.
- The initial /query call returns an acknowledgement (ResponseCode/ResponseDescription). This does not contain the final balance.
- Final account balances are delivered asynchronously to your ResultURL (ResultType/ResultCode = 0 indicates success).
Response helpers
- A returned AccountBalanceResponse model exposes:
- is_successful() helper that treats any all-zero ResponseCode string (e.g., "0" or "000") as success.
- Result callbacks are parsed into AccountBalanceResultCallback with:
- ResultParameter.ResultParameters list containing an 'AccountBalance' entry holding a delimited balance string.
Testing & Expected Behaviors
-
Query:
- Should return an AccountBalanceResponse acknowledgement. Confirm
is_successful()
forResponseCode == 0
. - HTTP client must receive the correct path and headers (
Authorization Bearer token + JSON content-type
).
- Should return an AccountBalanceResponse acknowledgement. Confirm
-
Result Callback:
- Parse Result.ResultParameter.ResultParameters and extract the AccountBalance key. Persist parsed balances for reconciliation.
- Ensure your handler returns the expected acknowledgement payload quickly so provider retries are avoided.
-
Timeout Callback:
- QueueTimeOutURL receives a Result with ResultType=1; handle accordingly (mark request as timed out, alert operations).
Next Steps
- Implement secure callback endpoints (IP restrictions, TLS).
- Persist both acknowledgements and final result notifications for audit and reconciliation.
- Add observability & alerting for timeout notifications and unexpected result codes.
Related Documentation
- 📡 Webhook Setup Guide - Best practices for building reliable endpoints
- 🏗️ Production Setup - Go-live checklist, security and monitoring