Python SDK watenga
Python 3.9 or later (httpx).
Installation
pip install watengaBefore the first release
pip install ./packages/watenga-pythonTake a test payment
Use a test secret key (sk_test_…) from the developer console. Test keys never move money: use the test cards and wallet numbers.
import os
from watenga import Watenga
watenga = Watenga(os.environ["WATENGA_SECRET_KEY"]) # sk_test_...
checkout = watenga.checkout.create(
amount="25.00",
currency="USD",
merchant_transaction_id="ORD-1001",
return_url="https://shop.example/return",
)
# Send the buyer to checkout["payment_url"]Receive the webhook
Verify the Watenga-Signature header against the raw request body before you trust an event. While you rotate the endpoint's signing secret, pass both secrets: either one verifies. On your machine, watenga listen --forward-to <your URL> (the CLI) forwards test events signed with a session secret.
from flask import Flask, request
from watenga import construct_event, SignatureVerificationError
@app.post("/webhooks")
def webhooks():
try:
event = construct_event(request.get_data(), request.headers.get("Watenga-Signature"), os.environ["WATENGA_WEBHOOK_SECRET"])
except SignatureVerificationError:
return "", 400
if event["type"] == "payment.capture.completed":
... # fulfil the order for event["data"]["object"]
return "", 200Errors
Every failed call raises one error type with the stable code, the HTTP status, the request_id to quote to support, the field at fault (param) and the code's page in the error reference.
from watenga import WatengaError
try:
watenga.refunds.create("txn_...", amount="5.00", reason="customer_request")
except WatengaError as err:
print(err.code, err.status_code, err.param, err.request_id, err.doc_url)Retries and idempotency
Network errors, 429 (after Retry-After) and 5xx answers are retried twice with backoff. Every write carries an Idempotency-Key, kept across those retries, so a retry is answered once and never applied twice; checkout.create uses checkout:<merchant_transaction_id>. See Idempotency.
# Every write gets an Idempotency-Key, kept across the SDK's own retries.
# When you retry a call yourself, pass the same key:
watenga.refunds.create("txn_...", reason="duplicate", idempotency_key="refund-ORD-1001")Pagination
Every list answers one page, { object: "list", data, has_more, next_cursor }; the auto-paginating form reads them all. See Pagination.
page = watenga.transactions.list(status="completed", limit=50)
# {"object": "list", "data": [...], "has_more": ..., "next_cursor": ...}
for txn in watenga.transactions.list_all(status="completed"):
print(txn["id"], txn["amount"], txn["currency"])Resources
The SDK sends Watenga-Version: 2026-10-29; fields use the API's names (snake_case) and money is a decimal string. The same method names in every language.
| Resource | Methods |
|---|---|
checkout | create, get_status |
checkout_sessions | create, retrieve, expire |
transactions | list, list_all, retrieve, get_mobile_status |
refunds | create |
payment_links | create, list, list_all, retrieve, update, deactivate, clone |
invoices | create, list, list_all, retrieve, update, send, remind, void |
quotations | create, list, list_all, retrieve, update, issue, void, convert_to_invoice |
plans | create, list, list_all, retrieve, update, delete |
subscriptions | create, list, list_all, retrieve, cancel, pause, resume, upcoming_invoice |
billing | record_usage, reverse_usage, check_entitlement |
customers | create, list, list_all, retrieve, update, timeline, set_tag |
payment_methods | create, list, list_all, retrieve, delete, charge |
account | get_balance, get_profile, get_api_key |
construct_event | webhook verification (several secrets while rotating) |
Resources come back as dicts; watenga.types has a TypedDict for each, generated from the OpenAPI document.
Refunds and payouts
refunds scope (Refunds). Payouts to your bank are dashboard-only, with two-step verification: they are not in the API or the SDKs.