Business Buy Goods
Make business-to-business payments to Buy Goods accounts using M-Pesa by initiating payment requests and handling asynchronous result notifications.
User Stories
- As a fintech product owner, I want to programmatically initiate B2B payments so that suppliers receive funds immediately after approval.
- 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 security credential of the initiator (base64 encoded). |
Amountrequired int | Integer | Transaction amount to be transferred between accounts. |
PartyArequired int | Integer | Shortcode from which money is deducted for the payment. |
PartyBrequired int | Integer | Shortcode to which money is credited (Buy Goods till number). |
AccountReferencerequired str | String | Account number associated with the payment. |
Requester str | String | Consumer's mobile number (optional). |
Remarksrequired str | String | Additional transaction information (maximum 100 characters). |
QueueTimeOutURLrequired str | String | HTTPS endpoint that will receive timeout notifications. |
ResultURLrequired str | String | HTTPS endpoint that will receive result notifications. |
Occassion str | String | Additional transaction information (optional). |
CommandID str | String | Command ID for the transaction (default: 'BusinessBuyGoods'). |
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 ID for the request message (returned in response). |
ConversationID str | String | Unique ID for the transaction (returned in response). |
ResponseCode str | String | Status code of the request (0 means success, returned in response). |
ResponseDescription str | String | Status message describing the request outcome (returned in response). |
Overview
Business Buy Goods enables businesses to make payments to Buy Goods 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 (
BusinessBuyGoods
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 Business Buy Goods payments with minimal boilerplate.
Quick Setup
Python
# Example: initiate Business Buy Goods payment using the high-level clientfrom mpesakit import MpesaClient
client = MpesaClient(consumer_key="...", consumer_secret="...", environment="sandbox")
resp = client.b2b.buygoods( initiator="API_Username", security_credential="encrypted_credential", amount=239, party_a=123456, party_b=654321, account_reference="353353", requester="254700000000", remarks="Payment for goods", result_url="https://your.example/result", queue_timeout_url="https://your.example/timeout", occassion="Purchase")
if resp.is_successful(): print("Business Buy Goods payment initiated successfully")else: print("Business Buy Goods payment failed:", resp.ResponseDescription)
💡Notes
- The facade returns typed Pydantic models (e.g.,
BusinessBuyGoodsResponse
) 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 Business Buy Goods Result and Timeoutfrom fastapi import FastAPI, Request, HTTPExceptionfrom mpesakit.business_buy_goods import BusinessBuyGoodsResultCallback, BusinessBuyGoodsResultCallbackResponse, BusinessBuyGoodsTimeoutCallback, BusinessBuyGoodsTimeoutCallbackResponsefrom mpesakit.security.ip_whitelist import is_mpesa_ip_allowed
app = FastAPI()
@app.post("/b2b/buygoods/result")async def buygoods_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 = BusinessBuyGoodsResultCallback(**payload) # will validate incoming fields # process the result (update database, notify user, etc.) ack = BusinessBuyGoodsResultCallbackResponse() return ack.model_dump(mode="json")
@app.post("/b2b/buygoods/timeout")async def buygoods_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 = BusinessBuyGoodsTimeoutCallbackResponse() 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 Business Buy Goods result asynchronously based on the data in the result callback.
Responses & Helpers
Example Business Buy Goods Success Response
{ "OriginatorConversationID": "5118-111210482-1", "ConversationID": "AG_20230420_2010759fd5662ef6d054", "ResponseCode": "0", "ResponseDescription": "Accept the service request successfully."}
💡Response Handling
- BusinessBuyGoodsResponse 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.b2b.buygoods(...)except Exception as exc: # The underlying HTTP client may raise exceptions on network errors; log and retry as appropriate print("Business Buy Goods payment 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
-
Business Buy Goods:
- The service posts to
/mpesa/b2b/v1/paymentrequest
with Authorization header set via TokenManager. - Responses are returned as
BusinessBuyGoodsResponse
instances. - The implementation tolerates a common provider typo ("OriginatorCoversationID") and maps it to
OriginatorConversationID
.
- The service posts to
-
Validation:
- Incoming payloads are validated against
BusinessBuyGoodsResultCallback
andBusinessBuyGoodsTimeoutCallback
. 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 Business Buy Goods 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