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
Parameter | Type | Description |
---|---|---|
Initiatorrequired str | String | M-Pesa API operator username (must be pre-approved by Safaricom). |
SecurityCredentialrequired str | String | Encrypted password of the API operator (base64 encoded). |
Amountrequired int | Integer | Transaction amount to be transferred for top-up. |
PartyArequired int | Integer | Shortcode from which money will be deducted for the top-up. |
PartyBrequired int | Integer | Shortcode to which money will be moved for the top-up. |
AccountReferencerequired str | String | Reference for the transaction. |
Requester str | String | Consumer's mobile number on behalf of whom you are paying (optional). |
Remarks str | String | Additional information for the transaction (optional). |
QueueTimeOutURLrequired str | String | HTTPS endpoint that will receive timeout notifications. |
ResultURLrequired str | String | HTTPS endpoint that will receive result notifications. |
CommandID str | String | Command ID for the transaction (default: 'BusinessPayToBulk'). |
SenderIdentifierType int | Integer | Identifier type for sender (default: 4 for shortcode). |
RecieverIdentifierType int | Integer | Identifier type for receiver (default: 4 for shortcode). |
OriginatorConversationID str | String | Unique request identifier assigned by Daraja (returned in response). |
ConversationID str | String | Unique request identifier assigned by M-Pesa (returned in response). |
ResponseCode str | String | Status code for request submission. 0 indicates success (returned in response). |
ResponseDescription str | String | Descriptive message of the request submission status (returned in response). |
Overview
B2C Account TopUp enables businesses to top up their accounts. It's an asynchronous operation with callbacks for results and timeouts.
💡Implementation Options
- Use the
MpesaClient
facade for simple and safe integration: it manages authentication, header injection and returns typed Pydantic models. - Use the Direct API (
B2CAccountTopUp
service +TokenManager
+HttpClient
) if you need full control over request/response handling, middleware, or custom error behaviors.
💡Why use MpesaClient
The facade handles token retrieval and attaches Authorization headers so you can call high-level operations like initiating B2C Account TopUp with minimal boilerplate.
Quick Setup
Python
# Example: initiate B2C Account TopUp using the high-level clientfrom 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)
💡Notes
- The facade returns typed
Pydantic models
(e.g.,B2CAccountTopUpResponse
) for ergonomic access to fields and helpers likeis_successful()
. - Authentication tokens are handled transparently by the client.
Webhook Handling (Result & Timeout)
Python
# Example: simple FastAPI endpoints for B2C Account TopUp Result and Timeoutfrom fastapi import FastAPI, Request, HTTPExceptionfrom mpesakit.b2c_account_top_up import B2CAccountTopUpCallback, B2CAccountTopUpCallbackResponse, B2CAccountTopUpTimeoutCallback, B2CAccountTopUpTimeoutCallbackResponsefrom 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")
# process timeout notification (log, retry logic, etc.) ack = B2CAccountTopUpTimeoutCallbackResponse() return ack.model_dump(mode="json")
💡Important behavior
- Result and timeout endpoints should return acknowledgements (ResultCode 0). This confirms receipt to Safaricom.
- Process the actual B2C Account TopUp result asynchronously based on the data in the result callback.
Responses & Helpers
Example B2C Account TopUp Success Response
{ "OriginatorConversationID": "5118-111210482-1", "ConversationID": "AG_20230420_2010759fd5662ef6d054", "ResponseCode": "0", "ResponseDescription": "Accept the service request successfully."}
💡Response Handling
- B2CAccountTopUpResponse provides is_successful() which normalizes success checks.
- The SDK normalizes minor provider response typos (e.g., 'OriginatorCoversationID') so fields are accessible reliably.
Error Handling
Python
# Handle errors when calling the servicetry: 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)
💡Notes
- HTTP or network errors raised by the HttpClient bubble up; wrap calls in try/except for robust production behavior.
- Tests exercise error flows to ensure exceptions propagate when the HTTP client fails.
Testing & Expected Behaviors
-
B2C Account TopUp:
- The service posts to
/mpesa/b2c/v3/paymentrequest
with Authorization header set via TokenManager. - Responses are returned as
B2CAccountTopUpResponse
instances. - The implementation tolerates a common provider typo ("OriginatorCoversationID") and maps it to
OriginatorConversationID
.
- The service posts to
-
Validation:
- Incoming payloads are validated against
B2CAccountTopUpCallback
andB2CAccountTopUpTimeoutCallback
. 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.
- Incoming payloads are validated against
Next Steps
💡What's Next?
- Implement robust webhook handlers for result and timeout notifications. Log and persist notifications to support reconciliation.
- Add observability and retry strategies around B2C Account TopUp calls and webhook processing to handle transient failures.
Related Documentation
- 📡 Webhook Setup Guide - Best practices for building reliable endpoints
- 🏗️ Production Setup - Go-live checklist, security and monitoring