Production-grade Python SDK for the ClickPesa API — sync & async, collections, payouts, BillPay and more
Project description
ClickPesa Python SDK
Production-grade Python SDK for the ClickPesa API — supports both sync and async usage, with automatic token management, checksum injection, retry logic, and a full exception hierarchy.
Features
- Sync & Async —
ClickPesafor blocking code,AsyncClickPesaforasyncio/ FastAPI / async frameworks - Auto Auth — JWT tokens are fetched and cached automatically (55-minute window, 1-hour API TTL)
- Checksum injection — HMAC-SHA256 checksums added to every mutating request when a
checksum_keyis configured - Retries — exponential backoff on transient 5xx errors (configurable)
- Thread-safe — safe to share a single client across threads or async tasks
- Context manager —
with/async withsupport for automatic cleanup - Typed exceptions — structured error hierarchy with
status_codeandresponseattributes - PEP 561 compliant — ships with
py.typedfor mypy / pyright support
Installation
pip install clickpesa-python-sdk
Requires Python 3.10+.
Quick Start
Sync
from clickpesa import ClickPesa
with ClickPesa(
client_id="YOUR_CLIENT_ID",
api_key="YOUR_API_KEY",
checksum_key="YOUR_CHECKSUM_KEY", # optional but recommended
sandbox=True, # set False for production
) as client:
balance = client.account.get_balance()
print(balance)
Async
import asyncio
from clickpesa import AsyncClickPesa
async def main():
async with AsyncClickPesa(
client_id="YOUR_CLIENT_ID",
api_key="YOUR_API_KEY",
checksum_key="YOUR_CHECKSUM_KEY",
sandbox=True,
) as client:
balance = await client.account.get_balance()
print(balance)
asyncio.run(main())
Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
client_id |
str |
required | Your ClickPesa application Client ID |
api_key |
str |
required | Your ClickPesa application API key |
checksum_key |
str | None |
None |
Enables HMAC-SHA256 checksum on every mutating request |
sandbox |
bool |
False |
Target sandbox (api-sandbox.clickpesa.com) instead of production |
timeout |
float |
30.0 |
Per-request timeout in seconds |
max_retries |
int |
3 |
Max retry attempts on transient server errors |
Note:
order_id/orderReferencevalues must be alphanumeric only (no hyphens, underscores, or special characters). The API will reject any order reference containing non-alphanumeric characters.
Collections
USSD Push
# 1. Preview — check available methods and fees before charging
preview = client.payments.preview_ussd_push(
amount="5000",
order_id="ORD20240001",
phone="255712345678", # optional: include to get sender details
fetch_sender_details=True, # optional: returns accountName / accountProvider
)
print(preview["activeMethods"]) # [{"name": "TIGO-PESA", "status": "AVAILABLE", "fee": 580, ...}]
# 2. Initiate — triggers PIN prompt on the customer's phone
transaction = client.payments.initiate_ussd_push(
amount="5000",
phone="255712345678",
order_id="ORD20240001",
currency="TZS", # only TZS supported
)
print(transaction["id"], transaction["status"])
Card Payment
# 1. Preview — check card method availability
preview = client.payments.preview_card(amount="50", order_id="CARD001")
# 2. Initiate — generate a hosted payment link
result = client.payments.initiate_card(
amount="50",
order_id="CARD001",
currency="USD", # only USD supported
customer={
"fullName": "John Doe",
"email": "john@example.com",
"phoneNumber": "255712345678",
},
# or use an existing customer ID:
# customer={"id": "CUST_123"}
)
print(result["cardPaymentLink"]) # redirect customer here
Query Payments
# Single payment by order reference
payments = client.payments.get_status("ORD20240001")
# Paginated list with filters
page = client.payments.list_all(
status="SUCCESS",
collectedCurrency="TZS",
startDate="2024-01-01",
endDate="2024-12-31",
limit=20,
skip=0,
)
print(page["totalCount"], page["data"])
Disbursements
Mobile Money Payout
# 1. Preview — verify fees and recipient before sending
preview = client.payouts.preview_mobile_money(
amount=10000,
phone="255712345678",
order_id="PAY20240001",
currency="TZS", # TZS or USD; recipient always receives TZS
)
print(preview["fee"], preview["receiver"]["accountName"])
# 2. Create — disburse funds
payout = client.payouts.create_mobile_money(
amount=10000,
phone="255712345678",
order_id="PAY20240001",
)
print(payout["id"], payout["status"]) # status: AUTHORIZED → PROCESSING → SUCCESS
Bank Payout (ACH / RTGS)
# Get list of supported banks and their BIC codes
banks = client.payouts.get_banks()
# [{"name": "EQUITY BANK TANZANIA LIMITED", "value": "equity_bank_tanzania_limited", "bic": "EQBLTZTZ"}, ...]
# 1. Preview
preview = client.payouts.preview_bank(
amount=500000,
account_number="1234567890",
bic="EQBLTZTZ",
order_id="BANK20240001",
transfer_type="ACH", # "ACH" or "RTGS"
currency="TZS",
)
# 2. Create
payout = client.payouts.create_bank(
amount=500000,
account_number="1234567890",
account_name="Jane Doe",
bic="EQBLTZTZ",
order_id="BANK20240001",
transfer_type="RTGS",
currency="TZS",
)
Query Payouts
# Single payout by order reference
payouts = client.payouts.get_status("PAY20240001")
# All payouts with filters
page = client.payouts.list_all(
channel="MOBILE MONEY",
status="SUCCESS",
limit=50,
)
BillPay
ClickPesa BillPay lets customers pay using a numeric control number through mobile money, SIM banking, and CRDB Wakalas. There are two types of control numbers:
- Order — one-time, closes after payment. Ideal for invoices and e-commerce orders.
- Customer — static and reusable per customer. Ideal for subscriptions and recurring payments.
Note: Every ClickPesa merchant has a 4-digit Merchant BillPay-Namba visible on the dashboard. Order control numbers can also be generated offline (no API call) by concatenating your Merchant BillPay-Namba with any internal order reference (e.g.
1122+231256=1122231256). The SDK only covers API-based generation.
Create Control Numbers
# Order control number (one-time)
cn = client.billpay.create_order_control_number(
bill_reference="INVOICE001", # optional — becomes the control number; auto-generated if omitted
amount=90900,
description="Water Bill - July 2024",
payment_mode="EXACT", # "EXACT" or "ALLOW_PARTIAL_AND_OVER_PAYMENT"
)
print(cn["billPayNumber"]) # share this with your customer
# Customer control number (persistent / recurring)
cn = client.billpay.create_customer_control_number(
customer_name="John Doe",
phone="255712345678", # phone or email required
email="john@example.com",
amount=50000,
payment_mode="ALLOW_PARTIAL_AND_OVER_PAYMENT",
)
Bulk Create (up to 50 per request)
# Bulk order control numbers
result = client.billpay.bulk_create_order_numbers([
{"billAmount": 10000, "billDescription": "Invoice #001", "billPaymentMode": "EXACT"},
{"billAmount": 20000, "billDescription": "Invoice #002"},
{"billReference": "MYREF003", "billAmount": 5000},
])
print(result["created"], result["failed"])
print(result["billPayNumbers"])
# Bulk customer control numbers
result = client.billpay.bulk_create_customer_numbers([
{"customerName": "Alice", "customerPhone": "255712345678", "billAmount": 15000},
{"customerName": "Bob", "customerEmail": "bob@example.com"},
])
Manage Existing Numbers
# Query details
details = client.billpay.get_details("55042914871931")
# Update amount, description or payment mode
client.billpay.update_reference(
"55042914871931",
amount=120000,
description="Updated Water Bill",
payment_mode="EXACT",
)
# Activate / deactivate
client.billpay.update_status("55042914871931", "INACTIVE")
client.billpay.update_status("55042914871931", "ACTIVE")
Hosted Links
# Checkout link — customer chooses their payment method
result = client.links.generate_checkout(
order_id="LINK001",
order_currency="TZS",
total_price="15000",
customer_name="Jane Doe",
customer_email="jane@example.com",
customer_phone="255712345678",
description="Order LINK001",
)
print(result["checkoutLink"]) # redirect customer here
# With itemised order instead of a flat total
result = client.links.generate_checkout(
order_id="LINK002",
order_currency="USD",
order_items=[
{"name": "Widget A", "price": "25.00", "quantity": 2},
{"name": "Widget B", "price": "10.00", "quantity": 1},
],
)
# Payout link — recipient enters their own bank / mobile details
result = client.links.generate_payout(amount="50000", order_id="POUT001")
print(result["payoutLink"])
Account & Exchange
# Account balances
result = client.account.get_balance()
# {"balances": [{"currency": "TZS", "balance": 39700}, {"currency": "USD", "balance": 0}]}
print(result["balances"])
# Transaction statement
statement = client.account.get_statement(
currency="TZS",
start_date="2024-01-01",
end_date="2024-12-31",
)
print(statement["accountDetails"])
print(statement["transactions"])
# Exchange rates
rates = client.exchange.get_rates() # all pairs
rates = client.exchange.get_rates(source="USD", target="TZS") # specific pair
# [{"source": "USD", "target": "TZS", "rate": 2510, "date": "..."}]
Async Usage
Every method on AsyncClickPesa is the await-able equivalent:
import asyncio
from clickpesa import AsyncClickPesa
async def run_payments():
async with AsyncClickPesa(
client_id="YOUR_CLIENT_ID",
api_key="YOUR_API_KEY",
sandbox=True,
) as client:
# Run multiple API calls concurrently
balance, rates = await asyncio.gather(
client.account.get_balance(),
client.exchange.get_rates(source="USD"),
)
print(balance, rates)
# Collections
tx = await client.payments.initiate_ussd_push(
amount="3000",
phone="255712345678",
order_id="ASYNC001",
)
# Disbursements
payout = await client.payouts.create_mobile_money(
amount=3000,
phone="255712345678",
order_id="ASYNC002",
)
asyncio.run(run_payments())
Webhook Verification
from clickpesa import WebhookValidator
# In your webhook endpoint (Flask / FastAPI / Django etc.)
def webhook_handler(request):
is_valid = WebhookValidator.verify(
payload=request.json,
signature=request.headers["X-ClickPesa-Signature"],
checksum_key="YOUR_CHECKSUM_KEY",
)
if not is_valid:
return {"error": "Invalid signature"}, 401
# Process event ...
Error Handling
All errors are subclasses of ClickPesaError and carry .status_code and .response:
from clickpesa.exceptions import (
AuthenticationError, # 401 — invalid credentials / expired token
ForbiddenError, # 403 — feature not enabled on your account
ValidationError, # 400 — bad payload
InsufficientFundsError, # 400 — not enough balance (subclass of ValidationError)
NotFoundError, # 404 — resource not found
ConflictError, # 409 — duplicate orderReference / billReference
RateLimitError, # 429 — payout request already in progress
ServerError, # 5xx — ClickPesa server error
ClickPesaError, # base class — catches all of the above
)
try:
client.payments.initiate_ussd_push("5000", "255712345678", "ORD001")
except InsufficientFundsError as e:
print(f"Not enough balance: {e}")
except ConflictError as e:
print(f"Order reference already used: {e}")
print(f"HTTP {e.status_code} — {e.response}")
except ClickPesaError as e:
print(f"Unexpected API error [{e.status_code}]: {e}")
Health Check
# Returns True if the API is reachable and credentials are valid
if client.is_healthy():
print("Connected to ClickPesa")
else:
print("API unreachable or credentials invalid")
# Async equivalent
healthy = await client.is_healthy()
Development
git clone https://github.com/JAXPARROW/clickpesa-python-sdk
cd clickpesa-python-sdk
# Install with dev dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Run tests with coverage
pytest --cov=clickpesa --cov-report=term-missing
License
MIT — see LICENSE for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file clickpesa_python_sdk-0.1.4.tar.gz.
File metadata
- Download URL: clickpesa_python_sdk-0.1.4.tar.gz
- Upload date:
- Size: 31.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c621ba54045149e121997a4f215f59636ec26263f19cfe81810c887fd82a3694
|
|
| MD5 |
ed92c77bf372498890dc4f504efb4714
|
|
| BLAKE2b-256 |
8b301534119f369ccacdfd8ee7dcf407e737fd3e282b5f31dc677d5336d68bfa
|
File details
Details for the file clickpesa_python_sdk-0.1.4-py3-none-any.whl.
File metadata
- Download URL: clickpesa_python_sdk-0.1.4-py3-none-any.whl
- Upload date:
- Size: 28.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd3c8a4b4c5a53c1ae8c694daa887122970988dc623851a950eef4a8080e52b2
|
|
| MD5 |
8cf2b1d8a1150003e9a631860917b348
|
|
| BLAKE2b-256 |
de1dc9ec969ca83a28039734b4244262800c9b08d227e1faa7125fd619fc4328
|