Skip to main content

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.

Parameters Definition

ParameterTypeDescription
Initiatorrequired
str
StringUsername used to initiate the request (API initiator identity).
SecurityCredentialrequired
str
StringEncrypted security credential (as required by Safaricom).
CommandIDrequired
str
StringTransaction type. Default: 'AccountBalance'.
PartyArequired
int
IntegerOrganization shortcode, till number or MSISDN depending on IdentifierType.
IdentifierTyperequired
int
IntegerType of PartyA. Allowed values: 1 (MSISDN), 2 (Till number), 4 (Short code).
Remarksrequired
str
StringFreeform comments for the transaction (must not exceed 100 characters).
QueueTimeOutURLrequired
str
StringCallback URL for timeout notifications (QueueTimeOutURL).
ResultURLrequired
str
StringCallback URL where final account balance result will be posted.

Response Schema

AccountBalanceResponse — the synchronous acknowledgement returned by client.balance.query(...)

ParameterTypeDescription
OriginatorConversationIDrequired
str | null
StringUnique identifier for the original request, generated by the API gateway.
ConversationIDrequired
str | null
StringUnique identifier for the transaction, generated by M-Pesa.
ResponseCoderequired
str | int
String/IntegerAcknowledgement status code. Any all-zero value (e.g. '0', '000') indicates the request was accepted.
ResponseDescriptionrequired
str
StringHuman readable acknowledgement message, e.g. 'Accept the service request successfully'.

Result Callback Schema

AccountBalanceResultCallback — posted to ResultURL once the balance query completes

ParameterTypeDescription
Result.ResultTyperequired
int
Integer0 = success, 1 = still waiting/timeout.
Result.ResultCoderequired
int | str
Integer/String0 indicates the balance query succeeded; any other value is a failure code.
Result.ResultDescrequired
str
StringHuman readable result description.
Result.OriginatorConversationIDrequired
str
StringMatches the OriginatorConversationID from the initial acknowledgement.
Result.ConversationIDrequired
str
StringMatches the ConversationID from the initial acknowledgement.
Result.TransactionID
str | null
StringM-Pesa transaction ID for this balance query, when available.
Result.ResultParameter.ResultParameters
list[{Key, Value}]
ArrayList of key/value pairs. The 'AccountBalance' key holds a pipe- and ampersand-delimited balance string per account (Working, Float, Utility, Charges Paid, Settlement); 'BOCompletedTime' holds the completion timestamp.
Result.ReferenceData.ReferenceItem.Key
str
StringReference parameter name, e.g. 'QueueTimeoutURL'.
Result.ReferenceData.ReferenceItem.Value
str
StringReference parameter value, e.g. the original QueueTimeoutURL you supplied.

Timeout Callback Schema

AccountBalanceTimeoutCallback — posted to QueueTimeOutURL if the query doesn't complete in time

ParameterTypeDescription
Result.ResultTyperequired
int
Integer1 for a timeout notification.
Result.ResultCoderequired
int | str
Integer/StringNon-zero code identifying the timeout.
Result.ResultDescrequired
str
Stringe.g. 'The service request timed out.'
Result.OriginatorConversationIDrequired
str
StringMatches the OriginatorConversationID from the initial acknowledgement.
Result.ConversationIDrequired
str
StringMatches the ConversationID from the initial acknowledgement.

Callback Acknowledgement Schemas

What your webhook handler should return to Safaricom

ParameterTypeDescription
ResultCode
int | str
Integer/StringDefaults to 0. Used by AccountBalanceResultCallbackResponse and AccountBalanceTimeoutCallbackResponse — the typed models to return from your /result and /timeout handlers respectively.
ResultDesc
str
StringDefaults to 'Result received and processed successfully.' (result) or 'Timeout notification received and processed successfully.' (timeout).

Overview

Query lifecycle: send AccountBalance request → receive immediate ack → process asynchronous result or timeout.

Quick Setup (Sync)

Python (example)
from mpesakit import MpesaClient
from mpesakit.account_balance import AccountBalanceIdentifierType
# ---- 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)

Quick Setup (Async)

Python (example)
import asyncio
from mpesakit import AsyncMpesaClient
from mpesakit.account_balance import AccountBalanceIdentifierType
async def main():
async with AsyncMpesaClient(
consumer_key="YOUR_KEY",
consumer_secret="YOUR_SECRET",
environment="sandbox",
) as client:
resp = await 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)
asyncio.run(main())

Webhook Handling (Result & Timeout)

Instead of manually constructing AccountBalanceResultCallback / AccountBalanceTimeoutCallback models from the raw payload, use the client's built-in process_account_balance_callback and process_account_balance_timeout helpers. They validate and parse the payload for you and are available on both MpesaClient and AsyncMpesaClient since they only do validation, not I/O.

Webhook Example (FastAPI)
from fastapi import FastAPI, Request, HTTPException
from mpesakit import MpesaClient
from mpesakit.account_balance import (
AccountBalanceResultCallbackResponse,
AccountBalanceTimeoutCallbackResponse,
)
from mpesakit.security.ip_whitelist import is_mpesa_ip_allowed
import logging
app = FastAPI()
logger = logging.getLogger("mpesa.account_balance")
client = MpesaClient(consumer_key="YOUR_KEY", consumer_secret="YOUR_SECRET", environment="sandbox")
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")
try:
# Validates and parses the payload into an AccountBalanceResultCallback object
callback = client.process_account_balance_callback(payload)
except Exception as exc:
logger.exception("Invalid AccountBalance result payload")
return AccountBalanceResultCallbackResponse(ResultCode=1, ResultDesc=f"Invalid payload: {exc}")
logger.info("AccountBalance result received: %s", callback.model_dump(mode="json"))
# e.g. extract and persist the AccountBalance entry from callback.Result.ResultParameters here
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:
# Validates and parses the payload into an AccountBalanceTimeoutCallback object
callback = client.process_account_balance_timeout(payload)
except Exception as exc:
logger.exception("Invalid AccountBalance timeout payload")
return AccountBalanceTimeoutCallbackResponse(ResultCode=1, ResultDesc=f"Invalid payload: {exc}")
logger.warning("AccountBalance timeout received: %s", callback.model_dump(mode="json"))
# Quick typed acknowledgement to stop retries
return AccountBalanceTimeoutCallbackResponse() # defaults to ResultCode=0, ResultDesc="Timeout notification received and processed successfully."

Important behaviors & validations

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, once parsed via process_account_balance_callback, are typed AccountBalanceResultCallback objects 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 for ResponseCode == 0. True whether called via client.balance.query(...) or await client.balance.query(...).
    • HTTP client must receive the correct path and headers (Authorization Bearer token + JSON content-type).
  • Result Callback:

    • Use process_account_balance_callback to 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; parse it with process_account_balance_timeout and handle accordingly (mark request as timed out, alert operations).

Next Steps