Skip to main content

paysawa

Python SDK for the Paysawa mobile money gateway (MTN Mobile Money and Orange Money, XAF). Collect payments, send money, create payment links and verify signed webhooks with a few lines of code. It speaks Paysawa's Campay-style API, so the concepts are the same as Campay's.

pip install paysawa

1. Get your credentials

In your Paysawa dashboard create an app. It gives you three values (you can show them again any time):

Value What it is
App username like pa_1a2b3c4d5e6f7a8b
App password secret, never put it in front-end code or in git
Webhook key verifies that webhooks really come from Paysawa

A test app uses the sandbox (no real money). A live app moves real money.

2. Quick start

from paysawa import Client

client = Client(
    app_username="pa_xxxxxxxxxxxxxxxx",
    app_password="your-app-password",
    webhook_key="whk_xxxxxxxx",
    base_url="https://YOUR-PAYSAWA-URL/api/",   # or set PAYSAWA_BASE_URL
)

# Ask a customer to pay: they get a prompt on their phone and approve with their PIN.
tx = client.collect(amount=5000, phone="670000001", description="Order 1001",
                    external_reference="order-1001", wait=True)   # wait=True blocks until final
if tx.is_successful:
    print("Paid!", tx.reference)
else:
    print("Not paid:", tx.status)

print(client.get_balance().total_balance)                          # your Paysawa balance
client.withdraw(amount=1000, phone="690000001", description="Refund")   # pay money out
link = client.get_payment_link(amount=2000, description="Order 1002", redirect_url="https://shop.example.com/thanks")
print(link.link)                                                    # send this to your customer

Put the credentials in environment variables instead of code and use Client.from_env(): PAYSAWA_APP_USERNAME, PAYSAWA_APP_PASSWORD, PAYSAWA_WEBHOOK_KEY, PAYSAWA_BASE_URL.

3. Statuses

Every transaction is PENDING, SUCCESSFUL or FAILED (tx.is_pending, tx.is_successful, tx.is_failed, tx.is_final). A collection stays PENDING until the customer approves. Without wait=True, check it later with client.get_transaction(reference) or use client.wait_for_transaction(reference, timeout=120), or rely on webhooks.

4. Webhooks

Set your app's webhook URL in the dashboard. Paysawa calls it with a GET and query parameters plus a signed signature. Always verify it:

# Flask
@app.get("/webhooks/paysawa")
def paysawa_webhook():
    try:
        event = client.verify_webhook(request.args.to_dict())
    except WebhookVerificationError:
        return "invalid", 401
    if event.is_successful:
        mark_order_paid(event.external_reference, event.amount)
    return "ok"
# FastAPI
@app.get("/webhooks/paysawa")
async def paysawa_webhook(request: Request):
    event = client.verify_webhook(dict(request.query_params))   # raises WebhookVerificationError if not authentic
    ...

Django: client.verify_webhook(request.GET.dict()). Reply with any 2xx status; otherwise Paysawa retries. Check event.amount against the order before you ship anything.

5. Async

from paysawa import AsyncClient

async with AsyncClient.from_env() as client:
    tx = await client.collect(5000, "670000001", "Order 1001", "order-1001", wait=True)

6. Errors

All errors inherit from PaysawaError (.message, .status, .code):

Exception When
AuthenticationError wrong app username/password
PermissionDeniedError live mode not enabled, account suspended
InvalidRequestError bad amount or phone number, unknown reference, limits
InsufficientFundsError withdrawal larger than your balance (amount + fee)
RateLimitError too many requests
ServerError / NetworkError Paysawa busy or unreachable
WaitTimeoutError wait_for_transaction gave up (.transaction has the latest state)
WebhookVerificationError the webhook is not authentic: ignore it

Payments (collect, withdraw) are never retried automatically, so a retry can never charge twice. Reads are retried twice. If a payment call fails with a network error, look the payment up by your external_reference before trying again.

7. Sandbox numbers (test apps)

670000001 succeeds, a number ending in 0000 fails, a number ending in 9999 stays pending.

Development

pip install -e ".[dev]"
pytest

Metadata

Release files for paysawa 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 paysawa 0.1.0
File Size Uploaded Provenance
paysawa-0.1.0.tar.gz 15.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for paysawa 0.1.0
File Interpreter ABI Platform
paysawa-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 26.9 kB

Release files / paysawa-0.1.0.tar.gz

Download URL paysawa-0.1.0.tar.gz
Size 15.1 kB
Tags Source
SHA-256 checksum
How to use checksums
1db7909fb330dae89cc76e4a83fb7d6cce5260616f0cf7acb8151b0d6f6e2d90
BLAKE2b-256 checksum
How to use checksums
09907b12655e99a0daba84053c0c93d4dc23adbb937f6ff5d2bdf01bf7747b4c
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 Oct 20, 2026.

Transparency log

Release files / paysawa-0.1.0-py3-none-any.whl

Download URL paysawa-0.1.0-py3-none-any.whl
Size 11.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5906da7fc77582615b09a2f9445f7ddf7f6045b305ef489303961553ca79822e
BLAKE2b-256 checksum
How to use checksums
331e52181c2a233c12ca6a6918ad7a0c08736738d1b018e9d2ce3433a7267984
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 Oct 20, 2026.

Transparency log

Security for paysawa 0.1.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for paysawa-0.1.0.tar.gz

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 Oct 20, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

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