Skip to main content

Async Python client for Paysell — accept TON & USDT with invoices, webhooks, and a polling fallback.

Release Package version Python Monthly downloads Pydantic v2 License GitHub Stars


Documentation: https://aiopaysell.readthedocs.io/

Source Code: https://github.com/paysell/aiopl

Paysell API docs: https://paysell.me/docs


aiopaysell wraps the Paysell merchant API: open an invoice, redirect the buyer to a hosted checkout page, and get a signed webhook the moment it's paid. Built with pydantic models throughout, a decorator-based event router in the style of aiogram, and a polling fallback for when a webhook can't reach you.

Key features:

  • Typed — full type annotations and pydantic v2 models for every request and response; mypy-clean.
  • Async — built on aiohttp, with a pooled, reusable connection instead of one per call.
  • Webhooks — signature and replay-window verification (HMAC-SHA256, ±5 min) done for you, for both payment.credited and payment.rejected; handlers via @pay.payment_credited(...), filterable with magic-filter.
  • Polling — a fallback for local dev or a missed webhook, tracking each invoice's real expires_at instead of a guessed timeout.
  • Honest amounts — pass 5 or Decimal("1.5") for normal units; the wire format, decimal-place validation, and the webhook's smallest-unit integers are handled for you.
  • Typed errors — one exception class per HTTP status, carrying the API's code/message, Retry-After, and field-level validation detail.

Requirements

Python 3.10+

aiopaysell depends on:

  • aiohttp — async HTTP transport.
  • pydantic — request/response models and validation.
  • magic-filter — event filters (F.status == "paid").
  • certifi — CA bundle for TLS.

Installation

$ pip install aiopaysell

---> 100%

Using the FastAPI webhook manager instead of aiohttp's:

$ pip install aiopaysell[fastapi]

Quick start

import asyncio
from aiopaysell import Paysell


async def main():
    pay = Paysell("sk_live_YOUR_KEY")

    invoice = await pay.create_invoice("TON", 5, order_id="order-1042")
    print(f"pay: {invoice.payment_url}")


if __name__ == "__main__":
    asyncio.run(main())

Webhook example

webhook_secret is not your API key — it's a separate secret shown once, next to the key, when you create it. Without it no delivery can be verified, and payment_credited never fires.

from aiohttp import web
from magic_filter import F
from aiopaysell import Paysell
from aiopaysell.webhook import AiohttpManager

app = web.Application()
pay = Paysell(
    "sk_live_YOUR_KEY",
    webhook_manager=AiohttpManager(app, path="/webhooks/paysell"),
    webhook_secret="YOUR_WEBHOOK_SECRET",
)


@pay.payment_credited(F.status == "paid")
async def on_paid(payment):
    print(f"order {payment.order_id} paid, {payment.credited_int} credited")


@pay.payment_rejected()
async def on_rejected(payment):
    print(f"order {payment.order_id}: deposit rejected ({payment.reason})")


if __name__ == "__main__":
    web.run_app(app, port=8080)

FastAPI works the same way via aiopaysell.webhook.FastAPIManager. No webhooks in local dev? Poll instead:

@pay.invoice_paid()
async def on_paid(invoice):
    print(invoice.status)


invoice = await pay.create_invoice("TON", 1.5)
invoice.poll()
await pay.start_polling()

Webhook and polling events live on separate routers (payment_credited/payment_rejected vs. invoice_paid/invoice_expired/invoice_cancelled) — running both is safe, just make handlers idempotent since the same payment could be reported by each.

Need your own checkout page instead of redirecting to payment_url? pay.get_public_invoice(invoice_id) reads the same unauthenticated endpoint the hosted page uses — no API key needed.

More in examples/: FastAPI webhooks, a standalone router for splitting handlers across modules, polling, a public-invoice checkout.

Good to know

  • webhook_secret is required for webhooks to work at all. It's shown once when the key is created, separately from the key itself. Passing webhook_manager= without it raises immediately; leaving both out (polling-only usage) is fine.
  • create_invoice(amount=...) takes normal units, like on an exchange5, 1.5, Decimal("1.5") — and formats them for you (decimal places validated against the coin). Pass a str if you already have it formatted and want it sent through unchanged.
  • The REST API and the webhook body disagree about units, on purpose. Invoice.amount is normal units, exactly what you sent; Invoice.amount_minor is the same as an integer smallest-unit string — use Invoice.amount_minor_int. Webhook payloads (PaymentCredited.amount, .credited, .fee) are smallest-unit integers throughout — use the matching *_int properties.
  • idempotency_key is yours to generate and persist per order; the library won't invent one for you, since its entire value is surviving a retry with the same key.
  • The webhook signature is HMAC-SHA256(secret, "{timestamp}.{raw_body}"), header names X-Paysell-Signature / X-Paysell-Timestamp. aiopaysell verifies both, including a ±5 minute replay window, before any handler runs.
  • Paysell defaults to Paysell's one production network — pass network= (a aiopaysell.client.network.Network) to point at a local backend for integration tests.

Errors

Status Exception
401 AuthenticationError
404 NotFoundError
409 ConflictError
422 InvalidRequestError
429 RateLimitError (.retry_after holds the Retry-After header, in seconds, when present)
502 BadGatewayError (safe to retry with the same idempotency_key)

All inherit aiopaysell.exceptions.APIErrorPaysellError.

License

MIT

Release files for aiopaysell 0.8.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aiopaysell 0.8.1
File Size Uploaded
aiopaysell-0.8.1.tar.gz 176.7 kB Details

Built distribution (wheel)

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

Total release size: 218.1 kB

Release files / aiopaysell-0.8.1.tar.gz

Download URL aiopaysell-0.8.1.tar.gz
Size 176.7 kB
Tags Source
SHA-256 checksum
How to use checksums
a466787e467907635ffe4c8c0f60c6d7fc4f6923180affd4b63afbeb1ecd4dee
BLAKE2b-256 checksum
How to use checksums
f686895b5701cb8ea1232630ced2ca2d047d0df68edab10b55879c034da22dda
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / aiopaysell-0.8.1-py3-none-any.whl

Download URL aiopaysell-0.8.1-py3-none-any.whl
Size 41.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fe22b8ab0a8043e5b7caf989f4fb0eaddfb312bac57a780ba984cb3ad27e8eee
BLAKE2b-256 checksum
How to use checksums
eee324a28abf0e32970b22c89dc43a95ac0cce1d273bbf50d7b5a0196042129d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.8.1 This release

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

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