Skip to main content

vonpay-checkout

Python SDK for the Von Payments Checkout API. Create hosted checkout sessions, verify webhook signatures, and validate signed return redirects.

Install

pip install vonpay-checkout

Requires: Python 3.9+, httpx 0.27+

Quick start

from vonpay.checkout import VonPayCheckout, VonPayError

client = VonPayCheckout("vp_sk_test_...")

session = client.sessions.create(
    amount=1499,
    currency="USD",
    country="US",
    success_url="https://example.com/order/123/confirm",
)

print(session.checkout_url)
# https://checkout.vonpay.com/checkout?session=vp_cs_test_...

Discrete-lifecycle API

The payment_intents, refunds, tokens, and capabilities namespaces give server-side control over payment intents, alongside the existing hosted-checkout sessions flow.

# Create a payment intent (auth + capture)
intent = client.payment_intents.create(
    amount=1499,
    currency="usd",
    capture_method="automatic",
    metadata={"order_id": "ord_42"},
    idempotency_key="ord_42-charge-1",
)
print(intent.id, intent.status)  # "vpi_live_…", "succeeded"

# Auth-only + later capture (fulfillment-on-ship)
auth = client.payment_intents.create(
    amount=1499, currency="usd", capture_method="manual", idempotency_key="ord_43-hold-1"
)
client.payment_intents.capture(auth.id, amount_to_capture=1499)

# Refund a captured intent (partial or full)
refund = client.refunds.create(payment_intent=intent.id, amount=500)

# Create a reusable token for save-card / MIT flows
token = client.tokens.create(buyer_id="buyer_42", setup_for_future_use="off_session")

# List a buyer's saved, active cards (newest first; page with next_cursor / has_more)
cards = client.payment_methods.list(buyer_id="buyer_42")

# Your payments, newest first, with filters (one page; at most a 31-day window)
declines = client.payment_intents.list(outcome=["bank_declined"], store="shop.example.com")
# Every page, one request at a time (stops on has_more False, or after max_items).
# To resume later, use list_pages(): it hands you each page's next_cursor (valid 24 h with the same filters).
for pi in client.payment_intents.list_auto_paging(created_gte="2026-10-01T00:00:00Z", max_items=500):
    print(pi.id, pi.outcome)
# Approval and decline rates, computed as the dashboard counts payment attempts.
# A rate is None -- not 0 -- when there was no traffic.
summary = client.payment_intents.summary(group_by=["day"], time_zone="America/New_York")
# For an agent, use a restricted key holding only payment_intents.read.

# A signed-in shopper removes their own card — PERMANENT. A repeat returns already_revoked=True.
client.tokens.revoke(cards.data[0].id, buyer_id="buyer_42", initiated_by="buyer")

# Apple Pay on your own checkout domain: register, host the file, verify
domain = client.wallet_domains.create(domain="shop.example.com", wallet_type="apple_pay")
file = client.wallet_domains.retrieve_file(domain.id)  # exact bytes, not JSON
# serve file.content at https://shop.example.com/.well-known/apple-developer-merchantid-domain-association
checked = client.wallet_domains.verify(domain.id)  # read checked.verification_status

# Read the merchant's processor capability matrix before invoking optional ops
caps = client.capabilities.get()
if caps.supported_operations.partial_refund:
    # partial refunds supported — safe to offer partial-refund UI
    ...

Send idempotency_key on every payment_intents.create. Use one value per charge and send the same value on every retry of that charge. Some payment connections already refuse a charge without one, and the announced 28 October 2026 cut-off has passed: the API may refuse it with 400 idempotency_key_required (nothing is charged). This SDK emits a one-time FutureWarning. 4.0.0 still accepts a charge without one; the key becomes required by this SDK in a later major version.

For a customer you have already signed in, create a one-time link to their subscription page (view, pause, resume, cancel, update card) when they press the button, and send them straight to it. The link opens once, within five minutes.

# On the button press. A fresh idempotency key is generated per call unless you
# pass one; never reuse one key across presses.
link = client.subscriptions.create_portal_link(
    "vsub_live_k7x9m2n4p3q5r6s8",
    return_url="https://shop.example.com/account",
)
# Return link.url to the page with "Cache-Control: no-store" and navigate from
# the browser (keep the # part -- the code is in it).

Never email, log or store the link, never put it in a query string, and never send it through anything that previews links. 404 subscription_not_found means there is nothing to manage (hide the button); 503 not_switched_on means links are not switched on for your account.

Subscription sign-up: show the terms, record the agreement

Read the product's terms before the order button, show them with your business name and how to cancel, then send what you showed on the sign-up. A missing or unusual record never refuses the charge: the response's warnings say what to fix.

from vonpay.checkout import PaymentMethodRef, RecurringBlock, RecurringConsent

# Before the page renders: the terms for the membership product.
terms = client.subscription_products.terms("633")
shown = terms.disclosure + " Cancel anytime from your account. Billed by EXAMPLE CO."

# On the order button: the card the shopper saved on this page, plus what they agreed to.
pi = client.payment_intents.create(
    payment_method=PaymentMethodRef(id=saved_card_id),
    buyer_email="shopper@example.com",
    buyer_ip_address=shopper_ip,
    recurring=RecurringBlock(
        product="633",
        consent=RecurringConsent(
            method="checkbox", accepted_at=clicked_at, disclosure_shown=shown, user_agent=user_agent
        ),
    ),
    idempotency_key=order_id,
)
for w in pi.warnings or []:
    logger.warning("[subscription] %s: %s", w.code, w.message)

shown must contain terms.price_phrase (case and spacing are ignored), or the response warns recurring_disclosure_differs. An unknown product is 404 subscription_product_not_found.

Migrating from 3.x

4.0.0 changes two behaviours. Nothing in the 3.x API was removed or renamed.

  1. A live secret key is sent only to Vonpay's API. With a vp_sk_live_* key, VonPayCheckout(...) raises ValueError unless base_url is unset or https on checkout.vonpay.com / checkout-staging.vonpay.com. Remove base_url, or, for a proxy you run on purpose, pass allow_custom_base_url=True (the environment variable VONPAY_ALLOW_CUSTOM_BASE_URL=1 on its own is deprecated: in 4.x it still opts a live key in when you omit the argument, with a one-time FutureWarning, and from 5.0.0 it is refused). Test and publishable keys are never refused; with an address that is not a Vonpay host or this machine they emit a one-time CustomBaseUrlWarning, which raises under -W error.
  2. An unknown money outcome says "check first". When a charge, capture, void or refund fails ambiguously and the SDK does not repeat it (err.retry_withheld is True), the error now has retryable=False and next_action="reconcile". Read the payment back (payment_intents.retrieve, or refunds.list for a refund) and send it again only if it did not happen, with the same idempotency_key.
  3. Webhook card can be absent from data on the charge, refund and saved-card events: read it with event.data.get("card").

Migrating from 2.x

3.0.0 changes two behaviours. Nothing else in the 2.x API was removed or renamed.

  1. payment_intents.void() is retried automatically only when you pass an idempotency key. After a timeout or a 500/502/503/504, 2.x retried a void on its own. 3.x does that only when the call carries a key, exactly as it already did for capture() and refunds.create(). Without one, the first error is raised with retry_withheld=True. To keep automatic retries:

    client.payment_intents.void(payment_intent_id, idempotency_key=f"{order_id}-void")
    
  2. A redirect from the API is now its own error, unexpected_redirect. The API never redirects, and this SDK has never followed one. 3.x raises a VonPayError with code == "unexpected_redirect", retryable is False and next_action == "check_configuration" (2.x raised a generic error). If you see it, check your base_url (the API origin itself, https://checkout.vonpay.com: https, no path), any proxy, and your network. Do not retry it. If you branch exhaustively on err.next_action, add the new "check_configuration" value.

Features

  • Typed session / webhook / error objects — full CheckoutSession, SessionStatus, WebhookEvent, VonPayError types.
  • Webhook verification — webhooks.construct_event(raw_body, signature_header, signing_secret) parses the x-vonpay-signature: t=<unix>,v1=<hex> header, verifies HMAC-SHA256 over f"{t}.{raw_body}" keyed by your per-endpoint signing secret (whsec_…), and enforces the freshness window (≤5 min old / ≤30 sec future). Accepts multiple v1= entries for zero-downtime secret rotation. A signing secret that is empty, is one of the example values from our samples and docs (such as whsec_your_endpoint_signing_secret), or is an API key is refused with a ValueError (TypeError for a non-string such as None) before anything is verified.
  • Test events — check event.test_event FIRST (2.7.0+). It is True only on an event synthesized by "Send test event"; livemode does not tell you this. Return 2xx and do nothing else — a test event can carry a real session's ids.
  • Re-read a payment — payment_intents.retrieve(id) (2.7.0+) returns its stored state. On a 3-D Secure return page use it, with the id you stored at create time, instead of the URL's query params.
  • Return confirmation — client.sessions.confirm_return(params) reads the session server-side (authenticated by your API key) and confirms the payment succeeded, returning ReturnOutcome(paid, signature_valid, status, reason). Use this, and branch on paid. Do not pass a second secret argument: returns are signed with a platform-wide secret no merchant holds, so a dashboard ss_* can never verify (signature_valid is None when no check ran). ⛔ verify_return_signature() is DEPRECATED — from checkout's next release, return links no longer carry sig / status / amount / currency / transaction_id, so there is nothing to verify and it refuses (returns False) every return without sig. It is removed in 5.0. Move to sessions.confirm_return(params) (no secret) now. (It was also never usable as merchant-side proof: the signature uses a platform-wide secret no merchant was ever issued.) It proves a message is AUTHENTIC, and a declined payment is signed just as validly, so a true from it was never proof of payment either. ⚠️ reason == "still_pending" means the charge is in flight (the ordinary 3-D Secure case) — show a neutral "confirming your payment", never a failure. ⚠️ status keeps reading succeeded after a refund, a dispute or a second payment, so confirm_return also reads the session's refund totals: refunded in full (or refunded and then disputed) is paid=False with reason="refunded" — do not fulfil, and show a neutral message, not "payment failed". A partial refund stays paid=True (your call). A second payment, or refund totals the server did not send, stays paid=True with needs_review=True and review_reason ("second_payment" / "refund_state_unknown"): show success, but have a person check before you fulfil. ⚠️ paid: true still does not mean safe to fulfil: record which session IDs you have already fulfilled, and prefer fulfilling from the charge.succeeded webhook. ⚠️ Not session.succeeded — that event is emitted internally but is not subscribable: it is absent from the merchant subscription catalog, which accepts an unknown event key, stores nothing and returns success. An endpoint subscribed to it receives nothing, forever, with no error raised at any layer. charge.succeeded is the fulfilment event.
  • Tie the return to YOUR order — any of your paid sessions answers paid=True, so a buyer could paste the return link of a different, cheaper order they paid for. Pass what your server recorded when it created the session: confirm_return(params, expected_session_id=..., expected_amount=..., expected_currency=...) (the session id, or the amount + currency pair, or both). A mismatch is paid=False with reason="session_mismatch" or "amount_mismatch", never paid; show a neutral "we couldn't match this payment to your order", not "payment failed". Optional; without it nothing changes.
  • Where your secret key may go — every request carries your secret key, so base_url is checked when the client is created. Leave it unset (https://checkout.vonpay.com serves live and test keys). ⚠️ A live secret key (vp_sk_live_*) is sent only to https on checkout.vonpay.com or checkout-staging.vonpay.com; any other address raises ValueError at construction, unless you pass allow_custom_base_url=True in your code (only for a proxy you run on purpose). The environment variable VONPAY_ALLOW_CUSTOM_BASE_URL=1 on its own is deprecated, because an environment can be set by something other than your code: in 4.x it still works when the argument is omitted, with a one-time FutureWarning; from 5.0.0 it is refused. An explicit allow_custom_base_url=False always wins over it. A test key may also use this machine (localhost, 127.0.0.1, [::1]); any other address is used with a one-time CustomBaseUrlWarning naming it (filter it under -W error with ignore::vonpay.checkout.CustomBaseUrlWarning). key_destination_problem(api_key, base_url, allow_custom=False) gives the same answer without creating a client (None, or the reason).
  • What kind of key is this — key_mode(key) reads the prefix: "test" (vp_sk_test_…), "live" (vp_sk_live_…), "publishable_test", "publishable_live" or "unknown" (anything else). held_as_live(key) is True for a live secret key and for every unknown key: use it to decide whether to ask a person before an action that moves money, so a key this SDK does not know is never treated as sandbox. Prefix only; never calls the API, never raises (4.7.0+).
  • Auto-retry — exponential backoff on 429 / 5xx with Retry-After header support.
  • When a payment's outcome is unknown — if a charge, capture, void or refund gets a 500/502/503/504 or a reply that cannot be read, and the SDK does not retry it, the error has retry_withheld=True, retryable=False and next_action="reconcile", whatever its code. Money may have moved: read the payment back (payment_intents.retrieve, or refunds.list for a refund) before anything else. If it did not happen and you send it again, reuse the same idempotency_key, never a new one. (RETRY_WITHHELD_HELP holds this advice.)
  • Plugin identification — building a plugin other merchants install? Pass app_info={"name": ..., "version": ..., "url": ...} and it is appended to the User-Agent of every request.
  • Request ID tracing — every response includes X-Request-Id for support tickets.

Documentation

License

MIT

Metadata

Release files for vonpay-checkout 4.7.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 vonpay-checkout 4.7.0
File Size Uploaded Provenance Actions
vonpay_checkout-4.7.0.tar.gz 350.4 kB With provenance Details for vonpay_checkout-4.7.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for vonpay-checkout 4.7.0
File Interpreter ABI Platform Actions
vonpay_checkout-4.7.0-py3-none-any.whl Python 3 none any Details for vonpay_checkout-4.7.0-py3-none-any.whl

Total release size: 618.7 kB

Release files / vonpay_checkout-4.7.0.tar.gz

Download URL vonpay_checkout-4.7.0.tar.gz
Size 350.4 kB
Tags Source
SHA-256 checksum
How to use checksums
dcadc8b58cda62a241644f787b6f2abf83460aa83365524ce4b7790c3e4def0c
BLAKE2b-256 checksum
How to use checksums
ce8459ed6c97ffd6ffe059d40d03b180d7d690f5cd6fc20c836badfc2efa6bb9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 4, 2026.

Transparency log

Release files / vonpay_checkout-4.7.0-py3-none-any.whl

Download URL vonpay_checkout-4.7.0-py3-none-any.whl
Size 268.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
56f8516a80b70c59f5058d1664234317f80bb4d8281879dda50b96dcad948ad3
BLAKE2b-256 checksum
How to use checksums
8041f366c398dcfbb1dfbe01af195d10dd08d08d1ca4f43a3415e0091c3a91e2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 4, 2026.

Transparency log

Security for vonpay-checkout 4.7.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for vonpay_checkout-4.7.0.tar.gz

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 Nov 4, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

4.7.0 This release

2 release files Full provenance

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