Skip to main content

crypto-processing-client

Python client for crypto-processing-api — per-user custodial BTC and USDT balances on top of your own BTCPay Server.

This is the client. The service it talks to is self-hosted; there is no hosted API to sign up for.

pip install crypto-processing-client

Five minutes

from crypto_processing_client import CryptoProcessingClient

client = CryptoProcessingClient("https://pay.example.com", api_key="cpk_live_...")

deposit = client.create_deposit(external_user_id="user-42", asset="BTC")
print(deposit.address, deposit.checkout_link)

# ... the user pays, and you poll or wait for a webhook ...

balances = client.get_user_balances("user-42")
for balance in balances.balances:
    print(balance.asset, balance.available)

withdrawal = client.create_withdrawal(
    external_user_id="user-42",
    asset="BTC",
    amount="25000000",  # gross, integer smallest units, a string
    destination_address="bc1q...",
)
print(withdrawal.status)  # pending_approval, or already moving

Every amount is a string, in both directions. amount and expected_amount are integer numbers of the asset's smallest unit ("25000000" is 0.25 BTC). Everything the server returns is a decimal string ("0.25000000"). Do not put either through float.

Every timestamp is an ISO 8601 string with a literal +00:00. They are strings here too, not datetimes, because the client must not re-render bytes an integrator may be comparing.

Idempotency, which you get for free

Every mutating call carries an Idempotency-Key. One is minted per call, and the same one is reused on every retry of that call — a retry with a new key is a second deposit, not a retry. Pass your own when your system already has an id for the operation:

client.create_deposit(external_user_id="user-42", asset="BTC", idempotency_key=f"order-{order.id}")

The client retries a 503 always, a 409 when the server sent a Retry-After (which is how "your earlier attempt is still running" is told apart from "this transition is illegal"), and a dropped connection — which is safe precisely because the key is pinned. Tune or switch it off:

from crypto_processing_client import RetryPolicy

client = CryptoProcessingClient(url, key, retry=RetryPolicy(attempts=1))

Webhooks

from crypto_processing_client import parse_event, UnknownEventTypeError, WebhookVerificationError


@app.post("/platform-webhook")
async def platform_webhook(request: Request):
    body = await request.body()  # the raw bytes, never the parsed JSON
    try:
        event = parse_event(body, request.headers, secret=WEBHOOK_SECRET)
    except WebhookVerificationError:
        return Response(status_code=401)
    except UnknownEventTypeError:
        return Response(status_code=200)  # a newer server sent a type you do not know

    if already_handled(event["id"]):  # the same evt_ id may arrive twice
        return Response(status_code=200)

    if event["type"] == "deposit.settled":
        deposit = client.get_deposit(event["data"]["deposit_id"])
        credit(deposit)  # act on the GET, not on the webhook
    return Response(status_code=200)

parse_event checks the signature over the raw body bytes, in constant time, inside a 300-second window — and refuses to give you an event if any of that fails. The verifier is checked against sdks/signature-vectors.json, the same vectors the server and the TypeScript client assert against, so the three cannot quietly disagree.

Step 5 is the whole contract: a webhook tells you something changed; the GET tells you what is true.

Errors

Every refusal raises. The split is by what you can do next:

Raised Status What to do
BadRequestError 400 fix the request
AuthenticationError 401 fix the key
PermissionDeniedError 403 the key lacks the scope
NotFoundError 404 no such deposit, withdrawal, user or asset
ConflictError 409 an illegal transition, if it reached you
ValidationError 422 a rule refused it; see .field_errors
UpstreamRefusedError 502 BTCPay said no; the intent is dead
ServiceUnavailableError 503 temporary; already retried, see .retry_after
ServerError other 5xx temporary
TransportError never got an answer

All of them derive from CryptoProcessingError.

Versions

client 0.N.x supports server 0.N.y. See docs/reference/versioning.md.

What is generated and what is not

crypto_processing_client._generated comes from the server's committed OpenAPI document and is regenerated in CI, which fails on any difference — so it cannot describe a server that does not exist. Everything else in this package is handwritten: the idempotency and retry behaviour, the error classes, and webhook verification. Those are the things codegen cannot produce.

MIT licensed, like the service.

Release files for crypto-processing-client 0.2.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 crypto-processing-client 0.2.0
File Size Uploaded
crypto_processing_client-0.2.0.tar.gz 50.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for crypto-processing-client 0.2.0
File Interpreter ABI Platform
crypto_processing_client-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size:156.6 kB

Release files / crypto_processing_client-0.2.0.tar.gz

Download URL crypto_processing_client-0.2.0.tar.gz
Size 50.5 kB
Tags Source
SHA-256 checksum
How to use checksums
8c9583c52a30099bceba8fcf75f48f234e43a95f70c319ac8dc321a3e855d646
BLAKE2b-256 checksum
How to use checksums
3075d3763662485441149d40529f21b189b0afa0c60f8a2ea2286a00bfbc9e70
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 Aug 11, 2026.

Transparency log

Release files / crypto_processing_client-0.2.0-py3-none-any.whl

Download URL crypto_processing_client-0.2.0-py3-none-any.whl
Size 106.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bcfef25d2473391f1968abcc4be994a5e04b0c1e41aba937251f25361fac4948
BLAKE2b-256 checksum
How to use checksums
16bcdd748ad65e8f73d1158d64a033f61bb09bbd4b3fc6f6265a07f7414bace3
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 Aug 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

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