Async Python client for Paysell — accept TON & USDT with invoices, webhooks, and a polling fallback.
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 bothpayment.creditedandpayment.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_atinstead of a guessed timeout. - Honest amounts — pass
5orDecimal("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_secretis required for webhooks to work at all. It's shown once when the key is created, separately from the key itself. Passingwebhook_manager=without it raises immediately; leaving both out (polling-only usage) is fine.create_invoice(amount=...)takes normal units, like on an exchange —5,1.5,Decimal("1.5")— and formats them for you (decimal places validated against the coin). Pass astrif you already have it formatted and want it sent through unchanged.- The REST API and the webhook body disagree about units, on purpose.
Invoice.amountis normal units, exactly what you sent;Invoice.amount_minoris the same as an integer smallest-unit string — useInvoice.amount_minor_int. Webhook payloads (PaymentCredited.amount,.credited,.fee) are smallest-unit integers throughout — use the matching*_intproperties. idempotency_keyis 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 namesX-Paysell-Signature/X-Paysell-Timestamp.aiopaysellverifies both, including a ±5 minute replay window, before any handler runs. Payselldefaults to Paysell's one production network — passnetwork=(aaiopaysell.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.APIError → PaysellError.
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)
| File | Size | Uploaded | |
|---|---|---|---|
| aiopaysell-0.8.1.tar.gz | 176.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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}
|