Paymos Python SDK
Official Python 3.10+ client for the Paymos Merchant API. It includes HMAC request signing, invoices, withdrawals, balances, static per-customer wallets and their deposit feed, bounded cursor iterators, structured errors, safe retries, and raw-body webhook verification.
pip install paymos-sdk
import os
from paymos import Paymos
paymos = Paymos(
api_key=os.environ["PAYMOS_API_KEY"],
api_secret=os.environ["PAYMOS_API_SECRET"],
)
invoice = paymos.invoices.create(
project_id="prj_...", amount="10.00", currency="USD",
external_order_id="order_123",
)
Use decimal strings for money. list() returns one cursor page; iterate()
follows cursors lazily and stops after 100 pages by default.
for invoice in paymos.invoices.iterate(status=["paid", "paid_over"], max_pages=10):
print(invoice["invoice_id"])
A payment channel is one payer's reusable set of deposit addresses. Create it, read its rails, then poll the confirmed-deposit feed and persist the cursor:
channel = paymos.payment_channels.create(project_id="prj_...", external_id="customer_42")
for rail in channel["networks"]:
print(rail["network"], rail["status"], rail.get("address", "not provisioned yet"))
feed = paymos.payment_channel_deposits.read(cursor=saved_cursor, limit=100)
for deposit in feed["items"]:
credit(deposit)
save_cursor(feed["next_cursor"])
Four things that bite a caller who guesses. Repeating the same external_id
returns the same channel — 200 instead of 201, and both bodies are a
channel, so calling this on every checkout is safe: a repeat is not a duplicate
and not an error. A rail's address is absent until that rail finishes
provisioning and never changes once it appears, so absent means "not yet", not
"no address". An absent minimum_deposit means "we cannot quote a minimum right
now", never "there is no minimum" — reading it as zero is how a merchant accepts
a deposit that lands below the live minimum and is never credited. And
next_cursor is never empty, not even on a page with no items: store it and
resume from it, and never loop until it is falsy the way iterate() ends a
list, because that loop either spins forever or stops on the first quiet page
and leaves reconciliation silently behind. confirmed_from is the first poll's
lower bound only; afterwards the stored cursor is the resume mechanism.
API failures raise typed subclasses of ApiError and preserve the HTTP status,
problem details, field, error code, headers, and Retry-After value.
Verify webhooks against the exact raw request body before parsing it:
from paymos import WebhookVerifier
event = WebhookVerifier(os.environ["PAYMOS_WEBHOOK_SECRET"]).construct_event(
request.headers["X-Webhook-Signature"], request.get_data()
)
The three payment_channel.deposit.* events each carry a full payment-channel deposit as
their data. construct_event returns it untyped, so bind it to PaymentChannelDeposit
yourself:
from typing import cast
from paymos import PaymentChannelDeposit
if event["event_type"] == "payment_channel.deposit.confirmed":
deposit = cast(PaymentChannelDeposit, event["data"])
if deposit["is_final"]:
credit(deposit["payment_channel_external_id"], deposit["net"]) # idempotent by event["event_id"]
confirming and reorged are advisory and may arrive out of order — a confirming can
land after the confirmed for the same deposit, and a reorged can be superseded by a
later confirmed. Credit only on payment_channel.deposit.confirmed with is_final true,
and never let an advisory event regress a deposit already known to be confirmed: crediting
on confirming releases goods against money a reorg can still take back.
Never use the API secret in browser or mobile code. Full documentation: https://paymos.io/docs/server-sdks
401 and 403 are different problems
A 401 means the credential is absent, malformed or unverifiable — fix the key or the signing clock, and stop retrying.
A 403 means the credential was accepted and something else refused. It covers two very different situations:
- the key is wrong for this call —
payout_key_required,payment_key_required,forbidden; - the key is fine, the account or project state says no —
whitelist_required,merchant_suspended,widget_inactive,terminal_not_enabled,not_sandbox.
A customer entering a payout address that is not whitelisted yet is the second kind: nothing is
wrong with your credentials, and paging the on-call engineer about them is the wrong move. Always
switch on the error code before deciding what to do.
PermissionError subclasses AuthenticationError, so except AuthenticationError written
before this split keeps catching 403.
Metadata
Release files for paymos-sdk 1.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| paymos_sdk-1.4.0.tar.gz | 22.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| paymos_sdk-1.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 38.5 kB
Release files / paymos_sdk-1.4.0.tar.gz
| Download URL | paymos_sdk-1.4.0.tar.gz |
|---|---|
| Size | 22.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7c36f15a61eceead2ad6bedf05428a89a7495d348b3b6e206c5e12e34953a7d1
|
|
BLAKE2b-256 checksum How to use checksums |
2cef155f87389fcc4498bb1f0d2030b7c812d39394e2a4eba5fa073a5bfe9cf4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
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 14, 2026.
Transparency logRelease files / paymos_sdk-1.4.0-py3-none-any.whl
| Download URL | paymos_sdk-1.4.0-py3-none-any.whl |
|---|---|
| Size | 16.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e825b5eb9a26ae9a170aa01dbd430d47ed63d03923bece4680232816a91aa71c
|
|
BLAKE2b-256 checksum How to use checksums |
070e4dc1a104de64809bdd7022d164e489da54367e1953e09a1d4fd5c7fdf538
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
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 14, 2026.
Transparency log