Skip to main content

B2C Account TopUp

Top up business accounts using M-Pesa by initiating account top-up requests and handling asynchronous result notifications.

User Stories

  • As a fintech product owner, I want to programmatically top up business accounts so that merchants receive funds immediately after customer payments.
  • As an integrations developer, I want a simple client and clear webhook callbacks so I can implement reliable end-to-end flows with minimal boilerplate.
  • As a billing operations engineer, I want result and timeout notifications with acknowledgements so I can reconcile transactions and trigger retries or alerts when needed.
  • As a reseller partner, I want a tested SDK and examples so I can onboard quickly and reduce integration defects.

Parameters Definition

ParameterTypeDescription
Initiatorrequired
str
StringM-Pesa API operator username (must be pre-approved by Safaricom).
SecurityCredentialrequired
str
StringEncrypted password of the API operator (base64 encoded).
Amountrequired
int
IntegerTransaction amount to be transferred for top-up.
PartyArequired
int
IntegerShortcode from which money will be deducted for the top-up.
PartyBrequired
int
IntegerShortcode to which money will be moved for the top-up.
AccountReferencerequired
str
StringReference for the transaction.
Requester
str
StringConsumer's mobile number on behalf of whom you are paying (optional).
Remarks
str
StringAdditional information for the transaction (optional).
QueueTimeOutURLrequired
str
StringHTTPS endpoint that will receive timeout notifications.
ResultURLrequired
str
StringHTTPS endpoint that will receive result notifications.
CommandID
str
StringCommand ID for the transaction (default: 'BusinessPayToBulk').
SenderIdentifierType
int
IntegerIdentifier type for sender (default: 4 for shortcode).
RecieverIdentifierType
int
IntegerIdentifier type for receiver (default: 4 for shortcode).

Response Schema

B2CAccountTopUpResponse — the synchronous acknowledgement returned by client.b2c.account_topup(...)

ParameterTypeDescription
OriginatorConversationIDrequired
str
StringUnique request identifier assigned by Daraja.
ConversationIDrequired
str
StringUnique request identifier assigned by M-Pesa.
ResponseCoderequired
str
StringStatus code for request submission. 0 indicates success.
ResponseDescriptionrequired
str
StringDescriptive message of the request submission status.

Overview

B2C Account TopUp enables businesses to top up their accounts. It's an asynchronous operation with callbacks for results and timeouts.

Quick Setup (Sync)

Python
# Example: initiate B2C Account TopUp using the high-level client
from mpesakit import MpesaClient
client = MpesaClient(consumer_key="...", consumer_secret="...", environment="sandbox")
resp = client.b2c.account_topup(
initiator="testapi",
security_credential="encrypted_credential",
amount=239,
party_a=600979,
party_b=600000,
account_reference="353353",
requester="254708374149",
remarks="Account top-up",
result_url="https://your.example/result",
queue_timeout_url="https://your.example/timeout"
)
if resp.is_successful:
print("B2C Account TopUp initiated successfully")
else:
print("B2C Account TopUp failed:", resp.ResponseDescription)

Quick Setup (Async)

Python
import asyncio
from mpesakit import AsyncMpesaClient
async def main():
async with AsyncMpesaClient(
consumer_key="...", consumer_secret="...", environment="sandbox"
) as client:
resp = await client.b2c.account_topup(
initiator="testapi",
security_credential="encrypted_credential",
amount=239,
party_a=600979,
party_b=600000,
account_reference="353353",
requester="254708374149",
remarks="Account top-up",
result_url="https://your.example/result",
queue_timeout_url="https://your.example/timeout"
)
if resp.is_successful:
print("B2C Account TopUp initiated successfully")
else:
print("B2C Account TopUp failed:", resp.ResponseDescription)
asyncio.run(main())

Webhook Handling (Result & Timeout)

Webhook handlers themselves don't need to be async just because you're calling the M-Pesa API asynchronously elsewhere in your app — async def route handlers work the same regardless of which client variant triggered the original request. The example below stays framework-async (FastAPI) while validating the callback payload directly against the response schemas.

Python
# Example: simple FastAPI endpoints for B2C Account TopUp Result and Timeout
from fastapi import FastAPI, Request, HTTPException
from mpesakit.b2c_account_top_up import B2CAccountTopUpCallback, B2CAccountTopUpCallbackResponse, B2CAccountTopUpTimeoutCallback, B2CAccountTopUpTimeoutCallbackResponse
from mpesakit.security.ip_whitelist import is_mpesa_ip_allowed
app = FastAPI()
@app.post("/b2c/account-topup/result")
async def account_topup_result(request: Request):
payload = await request.json()
caller_ip = (request.headers.get("x-forwarded-for") or request.client.host).split(",")[0].strip()
if not is_mpesa_ip_allowed(caller_ip):
raise HTTPException(status_code=403, detail="forbidden")
data = B2CAccountTopUpCallback(**payload) # will validate incoming fields
# process the result (update database, notify user, etc.)
ack = B2CAccountTopUpCallbackResponse()
return ack.model_dump(mode="json")
@app.post("/b2c/account-topup/timeout")
async def account_topup_timeout(request: Request):
payload = await request.json()
caller_ip = (request.headers.get("x-forwarded-for") or request.client.host).split(",")[0].strip()
if not is_mpesa_ip_allowed(caller_ip):
raise HTTPException(status_code=403, detail="forbidden")
data = B2CAccountTopUpTimeoutCallback(**payload) # will validate incoming fields
# process timeout notification (log, retry logic, etc.)
ack = B2CAccountTopUpTimeoutCallbackResponse()
return ack.model_dump(mode="json")

Result Callback Schema

B2CAccountTopUpCallback — posted to ResultURL once the top-up completes

ParameterTypeDescription
Result.ResultTyperequired
int
IntegerStatus code for the transaction sent to the listener.
Result.ResultCoderequired
int | str
Integer/StringTransaction result status code. 0 means success.
Result.ResultDescrequired
str
StringDescriptive message for the transaction result.
Result.OriginatorConversationIDrequired
str
StringUnique request identifier assigned by the API gateway.
Result.ConversationIDrequired
str
StringUnique request identifier assigned by M-Pesa.
Result.TransactionIDrequired
str
StringUnique M-Pesa transaction ID for the payment request.
Result.ResultParameters.ResultParameter
list[{Key, Value}]
ArrayTransaction details, e.g. DebitAccountBalance, Amount, DebitPartyAffectedAccountBalance, TransCompletedTime, DebitPartyCharges, ReceiverPartyPublicName, Currency, InitiatorAccountCurrentBalance.
Result.ReferenceData.ReferenceItem
list[{Key, Value}]
ArrayReference items, e.g. BillReferenceNumber, QueueTimeoutURL.

Timeout Callback Schema

B2CAccountTopUpTimeoutCallback — posted to QueueTimeOutURL if the top-up doesn't complete in time

ParameterTypeDescription
Result.ResultTyperequired
int
Integer1 indicates a timeout.
Result.ResultCoderequired
str
String'1' indicates a timeout.
Result.ResultDescrequired
str
StringDescription of the timeout event, e.g. 'The service request timed out.'
Result.OriginatorConversationIDrequired
str
StringUnique request identifier assigned by Daraja.
Result.ConversationIDrequired
str
StringUnique request identifier assigned by M-Pesa.

Callback Acknowledgement Schemas

What your webhook handler should return to Safaricom

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

Responses & Helpers

Example B2C Account TopUp Success Response
{
"OriginatorConversationID": "5118-111210482-1",
"ConversationID": "AG_20230420_2010759fd5662ef6d054",
"ResponseCode": "0",
"ResponseDescription": "Accept the service request successfully."
}

Error Handling

Python
# Handle errors when calling the service
try:
resp = client.b2c.account_topup(...)
except Exception as exc:
# The underlying HTTP client may raise exceptions on network errors; log and retry as appropriate
print("B2C Account TopUp failed:", exc)

Testing & Expected Behaviors

  • B2C Account TopUp:

    • The service posts to /mpesa/b2c/v1/paymentrequest with Authorization header set via TokenManager (or AsyncTokenManager).
    • Responses are returned as B2CAccountTopUpResponse instances, whether awaited from the async client or returned directly from the sync client.
    • The implementation tolerates a common provider typo ("OriginatorCoversationID") and maps it to OriginatorConversationID.
  • Validation:

    • Incoming payloads are validated against B2CAccountTopUpCallback and B2CAccountTopUpTimeoutCallback. Missing required fields or invalid formats will raise validation errors.
    • Use the provided response schemas for acknowledgements; invalid codes are rejected by the model validator.

Next Steps