Skip to main content

Dalipay Python SDK

Official Python SDK for the Dalipay Collections & Disbursements API - trigger mobile money checkout prompts (Tigo Pesa, Airtel Money, HaloPesa, AzamPesa) and send payouts, then track status by polling or via webhooks.

Only call this SDK from your backend - never from a browser, mobile app, or any client the public can inspect. If a key pair is exposed, revoke it immediately from your API keys settings and generate a new one.

Installation

pip install dalipay

Quick Start

Dalipay is self-hosted per merchant/gateway deployment, so there's no single shared API host - pass your gateway's own base URL.

from dalipay import Dalipay

client = Dalipay(
    public_key="gw_pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    secret_key="gw_sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    base_url="https://your-gateway-domain/api/v1",
)

collection = client.create_collection(
    account_number="0712345678",
    amount=1000,
    external_id="INV-00123",
    customer_name="Asha Mwakasege",
    # provider is optional - see "Provider auto-detection" below
)

print(f"UUID: {collection.uuid}")
print(f"Status: {collection.status}")  # always "processing" right after creation

Health Check

Dalipay doesn't publish a dedicated health-check endpoint, and every POST endpoint has real side effects (a USSD prompt, a money movement). is_healthy() instead does a read-only, side-effect-free probe: it checks collection status for a randomly generated uuid that's virtually guaranteed not to exist. A 404 proves the base URL is reachable and the key pair was accepted (bad credentials would 401 first); nothing is created and no phone number is contacted.

if not client.is_healthy():
    raise RuntimeError("Cannot reach Dalipay gateway")

Test vs. production

Every API key pair is tied to one environment - the environment is determined entirely by which key pair you send, there's no separate flag.

Key prefix Behavior
gw_pk_test_... / gw_sk_test_... Collections & settlements are simulated instantly - no real USSD prompt, no real money.
gw_pk_production_... / gw_sk_production_... Real money. Triggers an actual USSD prompt and real settlement.

Double-check which key pair you're using before testing against create_collection with a real phone number - production keys send a live payment prompt.

Collections

Create a collection

Triggers a mobile money checkout prompt on the customer's phone. The customer confirms or cancels on their device; the final result arrives asynchronously.

collection = client.create_collection(
    account_number="0712345678",   # Required
    amount=1000,                   # Required
    external_id="INV-00123",       # Required, your own reference, max 30 chars
    provider="Tigo",               # Optional: Tigo, Airtel, Halopesa, Azampesa, Mpesa
                                    #   - if omitted, guessed from account_number's prefix
    currency="TZS",                # Optional, defaults to TZS
    customer_name="Asha Mwakasege",  # Optional
)

print(collection.uuid)       # store this - you'll need it to check status
print(collection.reference)  # human-friendly gateway reference

Check collection status

Poll this to find out whether the customer confirmed or cancelled the prompt. Webhooks are faster for most integrations - use polling as a fallback or an on-demand "refresh".

collection = client.get_collection_status(collection.uuid)
print(collection.status)  # processing, success, or failed

Or block until it resolves (or times out) instead of polling by hand:

result = client.wait_for_collection(collection.uuid, timeout=300, interval=3)
print(result.status)  # success, failed, or still "processing" if the timeout elapsed
Status Meaning
processing Awaiting customer response
success Payment confirmed & balance credited
failed Cancelled, declined, or expired

A collection can stay processing for up to a few minutes. If you need a hard cutoff, treat anything still processing after ~5 minutes as likely abandoned, while still honoring a late success if it arrives.

There is no automatic retry: a cancelled, declined, or expired prompt is terminal (failed). To try again, call create_collection again with a fresh external_id.

Provider auto-detection

provider is optional on create_collection and create_disbursement - if you omit it, the SDK guesses it from account_number's prefix using guess_provider:

from dalipay import guess_provider

guess_provider("0755660639")  # "Mpesa"
guess_provider("0710000000")  # "Tigo"
guess_provider("0730000000")  # None - 073/TTCL has no mobile money provider on this API

Tanzanian numbers can be ported between networks, so a prefix doesn't guarantee the actual carrier - this is a convenience default, not a guarantee. If you already know the provider (e.g. the customer selected their network at checkout), pass it explicitly rather than relying on the guess. If the prefix can't be resolved (unrecognized, or 073/TTCL, which isn't one of the five supported providers), create_collection/create_disbursement raise ValueError and you must pass provider yourself.

Disbursements

Send a payout from your gateway balance to a mobile money account.

disbursement = client.create_disbursement(
    account_number="0712345678",     # Required
    amount=5000,                     # Required, between 1 and 5,000,000
    external_id="PAYOUT-00045",      # Required, your own reference, max 30 chars
    provider="Tigo",                 # Optional: Airtel, Tigo, Azampesa, Halopesa, Mpesa
                                      #   - if omitted, guessed from account_number's prefix
    recipient_name="Juma Hassan",    # Optional
    remarks="Agent commission",      # Optional
)

print(disbursement.reference)
print(disbursement.status)  # "success" in test mode, "awaiting_approval" in production

In production, the amount + fee is held from your balance immediately and the request waits for platform admin approval.

Check disbursement status

disbursement = client.get_disbursement_status(disbursement.reference)
print(disbursement.status)
Status Meaning
awaiting_approval Held, queued for admin review
success Sent to recipient
failed Send failed, balance refunded
rejected Declined by admin, balance refunded

Webhooks

If you configure a webhook URL in Settings, the gateway sends a signed POST request to it whenever a collection resolves.

from dalipay import verify_webhook, WebhookVerificationError

# In your webhook endpoint
try:
    payload = verify_webhook(
        body=request.body.decode(),
        signature=request.headers["X-Signature"],
        callback_secret="your_callback_secret",  # from Settings
    )

    if payload.event == "collection.success":
        # Mark the order matching payload.data.external_id as paid
        print(f"Collection {payload.data.reference} succeeded!")
    elif payload.event == "collection.failed":
        print(f"Collection {payload.data.reference} failed")

except WebhookVerificationError as e:
    print(f"Invalid webhook: {e}")

Respond with a 2xx status quickly. If your endpoint is slow or unreachable, the gateway logs the delivery attempt but does not currently retry - use status polling as a backstop for critical flows.

Webhook events

Event Description
collection.success Collection confirmed and balance credited
collection.failed Collection cancelled, declined, or expired

Async Support

For async applications (FastAPI, aiohttp, etc.):

from dalipay import AsyncDalipay

async def create_collection():
    async with AsyncDalipay(
        public_key="gw_pk_test_...",
        secret_key="gw_sk_test_...",
        base_url="https://your-gateway-domain/api/v1",
    ) as client:
        collection = await client.create_collection(
            account_number="0712345678",
            amount=1000,
            external_id="INV-00123",
        )
        result = await client.wait_for_collection(collection.uuid)
        return result

Error Handling

from dalipay import (
    Dalipay,
    ValidationError,
    AuthenticationError,
    PaymentRequiredError,
    ForbiddenError,
    NotFoundError,
    MethodNotAllowedError,
    ServerError,
)

try:
    collection = client.create_collection(...)
except ValidationError as e:
    print(f"Invalid request: {e.message}")        # 400
except AuthenticationError:
    print("Invalid API key pair")                  # 401
except PaymentRequiredError as e:
    print(f"Insufficient balance: {e.message}")     # 402
except ForbiddenError:
    print("IP not whitelisted, or KYC required")   # 403
except NotFoundError:
    print("Unknown collection/disbursement")        # 404
except MethodNotAllowedError:
    print("Wrong HTTP verb")                        # 405
except ServerError:
    print("Dalipay server error, try again later")  # 500

Every exception carries .message (human-readable) and .code (HTTP status).

Supported Providers

Tigo, Airtel, Halopesa, Azampesa, Mpesa

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dalipay-0.1.0.tar.gz (17.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dalipay-0.1.0-py3-none-any.whl (16.2 kB view details)

Uploaded Python 3

File details

Details for the file dalipay-0.1.0.tar.gz.

File metadata

  • Download URL: dalipay-0.1.0.tar.gz
  • Upload date:
  • Size: 17.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for dalipay-0.1.0.tar.gz
Algorithm Hash digest
SHA256 4cf0a50acd9c44c0d2ded156fa5baa41c4ce9637944655809afd53e12a8854fb
MD5 289718a366f85b9d31b6acafb6fb3a02
BLAKE2b-256 60f5aaae409869a83a2d2e6b166061b4cd6b419631eb376414ee201979b606d6

See more details on using hashes here.

File details

Details for the file dalipay-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: dalipay-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 16.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for dalipay-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ada7c5bd0bcc798ddb8d8a90357b9a06cf12cb9edb540c1e5c38d8f98c93e5a3
MD5 c8446c57218e5b15c5a9794de8b79d6f
BLAKE2b-256 d618a9a6e24e65fd35d9a57ded3feed39e928634b2beba3c4254952d11c528e0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page