lenco-py
Unofficial Python SDK for the Lenco API v2 — accounts, transfers, collections, settlements, transactions, and webhooks. Framework-agnostic: use it from Django, FastAPI, Flask, Celery, or plain scripts.
Not affiliated with or endorsed by Lenco.
Contents generated with DocToc
- Installation
- Quickstart
- Async (FastAPI)
- Django
- Webhooks
- Card collections (PCI DSS)
- Error handling
- Development
- Contributing
- License
Documentation: https://engineervix.github.io/lenco-py/
- Sync and async clients with identical resource APIs
- Fully typed (mypy strict), pydantic v2 models
- Webhook signature verification with the standard library only
- JWE card-payload encryption as an optional extra
Installation
pip install lenco-py
pip install "lenco-py[card]" # adds jwcrypto for card collections
Quickstart
from lenco import LencoClient
with LencoClient(token="your-api-token") as client:
# Accounts
for account in client.accounts.list().items:
print(account.id, account.currency, account.available_balance)
balance = client.accounts.balance("b176cda5-7d97-4a3f-b4dd-ab0234e9e08c")
# Verify a recipient before sending money
resolved = client.resolve.bank_account(account_number="9130000000000", bank_id="002")
print(resolved.account_name) # "Beata Jean"
# Send money (mobile money — Zambia)
transfer = client.transfers.to_mobile_money(
account_id="your-account-uuid",
amount=20.00,
reference="order-1234", # unique per transfer
phone="0977433571",
operator="airtel",
country="zm",
)
# Transfers always return HTTP 200 — inspect the status:
assert transfer.status == "successful", transfer.reason_for_failure
Async (FastAPI)
from fastapi import FastAPI
from lenco import AsyncLencoClient
app = FastAPI()
client = AsyncLencoClient(token="your-api-token")
@app.post("/payments/request")
async def request_payment(phone: str, amount: float):
collection = await client.collections.from_mobile_money(
amount=amount,
reference="order-5678",
phone=phone,
operator="mtn",
country="zm",
bearer="merchant",
)
# status is "pay-offline" until the customer approves on their phone
return {"reference": collection.reference, "status": collection.status}
Django
# payments/services.py
from lenco import LencoClient
from django.conf import settings
def charge_mobile_money(phone: str, operator: str, amount: float, reference: str):
with LencoClient(token=settings.LENCO_API_TOKEN) as client:
return client.collections.from_mobile_money(
amount=amount,
reference=reference,
phone=phone,
operator=operator,
country="zm",
)
For long-running use, prefer instantiating one client at module level and reusing it (the underlying httpx client pools connections).
Webhooks
Lenco signs every webhook with X-Lenco-Signature (HMAC-SHA512 of the raw body, keyed by the SHA256 of your API token). Always verify before processing:
from django.http import HttpRequest, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from lenco.exceptions import LencoWebhookVerificationError
from lenco.webhooks import parse_event, verify_signature
@csrf_exempt
def lenco_webhook(request: HttpRequest):
try:
verify_signature(
request.body,
request.headers["X-Lenco-Signature"],
api_token=settings.LENCO_API_TOKEN,
)
except LencoWebhookVerificationError:
return JsonResponse({"detail": "invalid signature"}, status=400)
event = parse_event(request.body)
if event.event == "collection.successful":
fulfill_order(event.data["reference"])
elif event.event == "transfer.failed":
alert_ops(event.data)
# Always ack quickly — Lenco retries every 30 min for 24 h otherwise.
return JsonResponse({"ok": True})
Event types: transfer.successful, transfer.failed, collection.successful, collection.failed, collection.settled, transaction.credit, transaction.debit.
Card collections (PCI DSS)
Card payloads are JWE-encrypted end-to-end. The SDK fetches a fresh RSA key per payload (Lenco rotates keys):
from lenco import LencoClient
with LencoClient(token="...") as client:
encrypted = client.encryption.encrypt({
"email": "customer@example.com",
"reference": "order-9012",
"amount": 13.00,
"currency": "ZMW",
"customer": {"firstName": "Haim", "lastName": "Hasegawa"},
"billing": {
"streetAddress": "1 Independence Ave",
"city": "Lusaka",
"postalCode": "10101",
"country": "ZM",
},
"card": {
"number": "5555555555554444",
"expiryMonth": "12",
"expiryYear": "2030",
"cvv": "123",
},
})
result = client.collections.from_card(encrypted_payload=encrypted)
if result.collection.status == "3ds-auth-required":
redirect_customer(result.authorization.redirect)
encrypt() also accepts a typed CardCollectionPayload instead of a dict, so a typo'd field name fails at construction instead of as an opaque 400 — see Card collections.
Error handling
from lenco import LencoAuthError, LencoNotFoundError, LencoValidationError, LencoError
try:
client.resolve.bank_account(account_number="0000", bank_id="002")
except LencoValidationError as e:
print(e.message) # "Account details was not found"
except LencoAuthError:
# 401 — check your token
...
except LencoNotFoundError:
# 404
...
except LencoError:
# network failure, 5xx, anything else
...
Note the envelope gotcha: Lenco can return HTTP 200 with {"status": false}. The SDK treats that as an error and raises — you never have to check the envelope yourself. But for transfer and collection initiation, a 200 with "status": true means the request was accepted. The outcome lives in transfer.status / collection.status ("successful", "pending", "failed", "pay-offline", …).
Development
python3.11 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest # test suite (mocked transport, no network)
mypy src # strict type checking
ruff check src # lint
black src tests # format
lefthook install # one-time: run ruff/black/mypy on commit, lint commit messages
Docs (VitePress, Node 22+ and just — docs/ has its own package.json, wrapped by the root justfile):
just docs-install
just docs-dev # local dev server
just docs-build # production build
Contributing
See CONTRIBUTING.md.
License
BSD-3-Clause. See LICENSE.
Metadata
Release files for lenco-py 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| lenco_py-0.1.0.tar.gz | 24.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lenco_py-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 54.4 kB
Release files / lenco_py-0.1.0.tar.gz
| Download URL | lenco_py-0.1.0.tar.gz |
|---|---|
| Size | 24.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7967273416144dcde517f7fa5995d35e6f56c1cdef41b3085fa2f70193d7ad04
|
|
BLAKE2b-256 checksum How to use checksums |
11173eb68d3025496b5f298df4b38a70a56d9e4b4383ecf4c6ad8a485d8f3cce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.
Transparency logRelease files / lenco_py-0.1.0-py3-none-any.whl
| Download URL | lenco_py-0.1.0-py3-none-any.whl |
|---|---|
| Size | 30.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
468415d9876b35a728d41ed82d562def78635db2cc88a34c51d0a7f066477afb
|
|
BLAKE2b-256 checksum How to use checksums |
3de48cddddf6c5ea0f6a084517589d572bb3c73a799e1c2157a32075e82b5692
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.
Transparency log