Skip to main content

Customer to Business (C2B)

Manage Customer-to-Business (C2B) flows: register Validation and Confirmation endpoints with Safaricom, validate incoming payments, and send confirmations/acknowledgements.

User Stories

  • As a fintech product owner, I want to programmatically manage C2B payments so that customers 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

ParameterTypeDescription
ShortCoderequired
int
IntegerOrganization's PayBill or Till shortcode to register URLs for.
ResponseTyperequired
str
StringDefault behavior if ValidationURL cannot be reached. Allowed: 'Completed' or 'Cancelled'.
ConfirmationURLrequired
str
StringHTTPS endpoint that will receive payment confirmation notifications.
ValidationURLrequired
str
StringHTTPS endpoint that will receive validation callbacks before accepting payments.

URL Registration Response Schema

C2BRegisterUrlResponse — the acknowledgement returned by client.c2b.register_url(...)

ParameterTypeDescription
OriginatorConversationIDrequired
str | null
StringUnique ID for the registration request.
ResponseCoderequired
str | int
String/IntegerStatus code. Any all-zero value (e.g. '0') indicates success.
ResponseDescriptionrequired
str
StringStatus message, e.g. 'success'.

Validation & Confirmation Callback Schema

C2BValidationRequest — the payment payload posted to both ValidationURL and ConfirmationURL

Safaricom posts the same payload shape to both URLs: a validation request before the payment is accepted (only if external validation is enabled on your shortcode), and a confirmation notice once the payment has completed.

ParameterTypeDescription
TransactionTyperequired
str
StringType of transaction (e.g. 'Pay Bill', 'Buy Goods').
TransIDrequired
str
StringUnique M-Pesa transaction identifier.
TransTimerequired
str
StringTimestamp of transaction in YYYYMMDDHHmmss format.
TransAmountrequired
float
FloatAmount transacted (whole numbers expected by M-Pesa).
BusinessShortCoderequired
int
IntegerReceiving organization's shortcode.
BillRefNumber
str | null
StringAccount/reference number supplied by payer (PayBill only, max 20 chars).
InvoiceNumber
str | null
StringInvoice number, when applicable.
OrgAccountBalance
str | null
StringOrganization account balance after the payment.
ThirdPartyTransID
str | null
StringPartner transaction ID, when applicable.
MSISDNrequired
int | str
Integer/StringCustomer mobile number making the payment.
FirstName
str | null
StringCustomer's first name, when known to M-Pesa.
MiddleName
str | null
StringCustomer's middle name, when known to M-Pesa.
LastName
str | null
StringCustomer's last name, when known to M-Pesa.

Validation Response Schema

C2BValidationResponse — what your /validation handler should return

ParameterTypeDescription
ResultCoderequired
str | int
String/Integer'0' to accept the payment, or one of the C2B validation error codes to reject it (see Validation Result Codes below).
ResultDescrequired
str
StringShort description, e.g. 'Accepted' or 'Rejected' (<= 90 chars recommended).
ThirdPartyTransID
str | null
StringOptional partner transaction id to echo back.

Confirmation Acknowledgement Schema

C2BConfirmationResponse — what your /confirmation handler should return

ParameterTypeDescription
ResultCode
int | str
Integer/StringDefaults to 0 (success).
ResultDesc
str
StringDefaults to 'Success'.

Overview

C2B (Customer-to-Business) covers URL registration with Safaricom (so their platform can call your services), validating incoming payments and acknowledging confirmations.

Quick Setup (Sync)

Python
# Example: register C2B URLs using the high-level client
from mpesakit import MpesaClient
from mpesakit.c2b import C2BResponseType
client = MpesaClient(consumer_key="...", consumer_secret="...", environment="sandbox")
resp = client.c2b.register_url(
short_code=600999,
response_type=C2BResponseType.COMPLETED,
confirmation_url="https://your.example/confirmation",
validation_url="https://your.example/validation",
)
if resp.is_successful:
print("Registration accepted")
else:
print("Registration failed:", resp.ResponseDescription)

Quick Setup (Async)

Python
import asyncio
from mpesakit import AsyncMpesaClient
from mpesakit.c2b import C2BResponseType
async def main():
async with AsyncMpesaClient(
consumer_key="...", consumer_secret="...", environment="sandbox"
) as client:
resp = await client.c2b.register_url(
short_code=600999,
response_type=C2BResponseType.COMPLETED,
confirmation_url="https://your.example/confirmation",
validation_url="https://your.example/validation",
)
if resp.is_successful:
print("Registration accepted")
else:
print("Registration failed:", resp.ResponseDescription)
asyncio.run(main())

Webhook Handling (Validation & Confirmation)

Validation and Confirmation callbacks are inbound HTTP POSTs from Safaricom — independent of whether the URLs were registered via the sync or async client. Handlers stay framework-async either way (FastAPI shown below), and validate the payload directly against the SDK's request/response schemas.

Python
# Example: simple FastAPI endpoints for Validation and Confirmation
from fastapi import FastAPI, Request, HTTPException
from mpesakit.c2b import C2BValidationRequest, C2BValidationResponse, C2BConfirmationResponse
from mpesakit.security.ip_whitelist import is_mpesa_ip_allowed
app = FastAPI()
@app.post("/c2b/validation")
async def validation(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 = C2BValidationRequest(**payload) # will validate incoming fields
# perform business checks (account exists, limits, etc.)
result = C2BValidationResponse(ResultCode="0", ResultDesc="Accepted", ThirdPartyTransID=data.ThirdPartyTransID)
return result.model_dump(mode="json")
@app.post("/c2b/confirmation")
async def confirmation(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 final payment notification (store transaction, update balance, etc.)
ack = C2BConfirmationResponse() # ResultCode=0, ResultDesc="Success"
return ack.model_dump(mode="json")

Validation Result Codes

Common Result Codes
{
"0": "Accepted",
"C2B00011": "Invalid MSISDN",
"C2B00012": "Invalid Account Number",
"C2B00013": "Invalid Amount",
"C2B00014": "Invalid KYC Details",
"C2B00015": "Invalid Shortcode",
"C2B00016": "Other Error"
}

Responses & Helpers

Example Register URL Success Response
{
"OriginatorConversationID": "7619-37765134-1",
"ResponseCode": "0",
"ResponseDescription": "success"
}

Validation & Safety Checks

Error Handling

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

Testing & Expected Behaviors

  • Register URL:

    • The service posts to /mpesa/c2b/v1/registerurl with Authorization header set via TokenManager (or AsyncTokenManager).
    • Responses are returned as C2BRegisterUrlResponse 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 C2BValidationRequest. Missing required fields or invalid formats will raise validation errors.
    • Use the provided enums for allowed ResultCode values; invalid codes are rejected by the model validator.
  • Confirmation:

    • Return a C2BConfirmationResponse (ResultCode 0 and ResultDesc "Success") to acknowledge receipt.

Next Steps