Skip to main content

Tax Remittance

Remit taxes to KRA using M-Pesa by initiating tax payment requests and handling asynchronous result notifications.

User Stories

  • As a fintech product owner, I want to programmatically remit taxes to KRA so that I can automate compliance processes.
  • 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
StringUsername used to initiate the request (must be pre-approved by Safaricom).
SecurityCredentialrequired
str
StringEncrypted security credential of the initiator (base64 encoded).
Amountrequired
int
IntegerTransaction amount in KES to be remitted to KRA.
PartyArequired
int
IntegerShortcode from which money is deducted for tax remittance.
AccountReferencerequired
str
StringPayment Registration Number (PRN) issued by KRA for the tax payment.
Remarksrequired
str
StringAdditional information for the transaction (maximum 100 characters).
QueueTimeOutURLrequired
str
StringHTTPS endpoint that will receive timeout notifications.
ResultURLrequired
str
StringHTTPS endpoint that will receive result notifications.
PartyB
int
IntegerKRA shortcode (default: 572572).
CommandID
str
StringCommand ID for the transaction (default: 'PayTaxToKRA').
SenderIdentifierType
int
IntegerIdentifier type for sender (default: 4 for shortcode).
RecieverIdentifierType
int
IntegerIdentifier type for receiver (default: 4 for shortcode).

Response Schema

TaxRemittanceResponse — the synchronous acknowledgement returned by client.tax.remittance(...)

ParameterTypeDescription
OriginatorConversationIDrequired
str | null
StringUnique ID for the request message.
ConversationIDrequired
str | null
StringUnique ID for the transaction.
ResponseCoderequired
str | int
String/IntegerStatus code of the request. 0 means success.
ResponseDescriptionrequired
str
StringStatus message describing the request outcome.

Overview

Tax Remittance enables businesses to pay taxes directly to KRA through M-Pesa. It's an asynchronous operation with callbacks for results and timeouts.

Quick Setup (Sync)

Python
# Example: initiate tax remittance using the high-level client
from mpesakit import MpesaClient
client = MpesaClient(consumer_key="...", consumer_secret="...", environment="sandbox")
resp = client.tax.remittance(
initiator="TaxPayer",
security_credential="encrypted_credential",
amount=239,
party_a=888880,
account_reference="353353",
remarks="Tax payment for Q1",
result_url="https://your.example/result",
queue_timeout_url="https://your.example/timeout"
)
if resp.is_successful:
print("Tax remittance initiated successfully")
else:
print("Tax remittance 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.tax.remittance(
initiator="TaxPayer",
security_credential="encrypted_credential",
amount=239,
party_a=888880,
account_reference="353353",
remarks="Tax payment for Q1",
result_url="https://your.example/result",
queue_timeout_url="https://your.example/timeout"
)
if resp.is_successful:
print("Tax remittance initiated successfully")
else:
print("Tax remittance failed:", resp.ResponseDescription)
asyncio.run(main())

Webhook Handling (Result & Timeout)

The callback is an inbound HTTP POST from Safaricom/Daraja — independent of whether the remittance was filed via MpesaClient or AsyncMpesaClient. For the result payload, both clients expose process_tax_remittance_callback, a one-call shorthand for TaxRemittanceResultCallback.model_validate(payload); it's plain validation with no I/O, so it's never awaited even on the async client. There's no equivalent helper for the timeout payload, so that one is still validated directly against TaxRemittanceTimeoutCallback.

Python
# Example: simple FastAPI endpoints for Tax Remittance Result and Timeout
from fastapi import FastAPI, Request, HTTPException
from mpesakit import MpesaClient # or AsyncMpesaClient — process_tax_remittance_callback is identical on both
from mpesakit.tax_remittance import TaxRemittanceResultCallbackResponse, TaxRemittanceTimeoutCallback, TaxRemittanceTimeoutCallbackResponse
from mpesakit.security.ip_whitelist import is_mpesa_ip_allowed
app = FastAPI()
client = MpesaClient(consumer_key="...", consumer_secret="...", environment="sandbox")
@app.post("/tax/result")
async def tax_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")
# Equivalent to TaxRemittanceResultCallback.model_validate(payload)
data = client.process_tax_remittance_callback(payload)
# process the result (update database, notify user, etc.)
ack = TaxRemittanceResultCallbackResponse()
return ack.model_dump(mode="json")
@app.post("/tax/timeout")
async def tax_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 = TaxRemittanceTimeoutCallback(**payload) # will validate incoming fields
# process timeout notification (log, retry logic, etc.)
ack = TaxRemittanceTimeoutCallbackResponse()
return ack.model_dump(mode="json")

Result Callback Schema

TaxRemittanceResultCallback — posted to ResultURL once the remittance completes

ParameterTypeDescription
Result.ResultTyperequired
int
Integer0 = success, 1 = failure.
Result.ResultCoderequired
int | str
Integer/String0 indicates the remittance succeeded; any other value is a failure code.
Result.ResultDescrequired
str
StringHuman readable result description.
Result.OriginatorConversationIDrequired
str
StringMatches the OriginatorConversationID from the initial request.
Result.ConversationIDrequired
str
StringMatches the ConversationID from the initial acknowledgement.
Result.TransactionID
str | null
StringM-Pesa transaction ID for the remittance, when successful.
Result.ResultParameters.ResultParameter
list[{Key, Value}]
ArrayTransaction details, e.g. Amount, Currency, TransCompletedTime.
Result.ReferenceData.ReferenceItem
list[{Key, Value}]
ArrayReference items, e.g. BillReferenceNumber, QueueTimeoutURL.

Timeout Callback Schema

TaxRemittanceTimeoutCallback — posted to QueueTimeOutURL if the remittance doesn't complete in time

Uses the same Result shape as the Result Callback Schema above (it's the same TaxRemittanceResultMetadata model), just delivered to QueueTimeOutURL instead of ResultURL, typically with a non-zero ResultCode and without TransactionID/ResultParameters.

Callback Acknowledgement Schemas

What your webhook handler should return to Safaricom

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

Responses & Helpers

Example Tax Remittance 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.tax.remittance(...)
except Exception as exc:
# The underlying HTTP client may raise exceptions on network errors; log and retry as appropriate
print("Tax remittance failed:", exc)

Testing & Expected Behaviors

  • Tax Remittance:

    • The service posts to /mpesa/b2bpayment/v1/remittax with Authorization header set via TokenManager (or AsyncTokenManager).
    • Responses are returned as TaxRemittanceResponse 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 TaxRemittanceResultCallback (directly, or via process_tax_remittance_callback) and TaxRemittanceTimeoutCallback. 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