Skip to main content

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)

Source distribution for paymos-sdk 1.4.0
File Size Uploaded
paymos_sdk-1.4.0.tar.gz 22.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for paymos-sdk 1.4.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

1.4.0 This release

2 release files

1.1.1

2 release files

1.0.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