Skip to main content

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.

PyPI PyPI - Python Version PyPI - Downloads License: BSD-3-Clause

CI Coverage Ruff Code style: black Checked with mypy

Python Pydantic Pytest

Contents generated with DocToc

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)

Source distribution for lenco-py 0.1.0
File Size Uploaded
lenco_py-0.1.0.tar.gz 24.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lenco-py 0.1.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page