Python API SDK
Install the StableOps Python SDK to create payment orders and checkout sessions, manage Webhook endpoints, verify signatures, and handle API errors and replay.
Install
pip install stableopsRequires Python 3.8+ and depends on httpx and pydantic v2. It ships both a
synchronous StableOps and an asynchronous AsyncStableOps client with an
identical API shape.
Want a runnable end-to-end example?
Configure
import os
from stableops import StableOps
client = StableOps(
api_key=os.environ["STABLEOPS_API_KEY"],
# Optional:
# base_url="https://api.stableops.dev",
# timeout=30.0,
# max_retries=2,
# checkout_base_url="https://pay.stableops.dev", # only affects checkout url building
)The environment (sandbox / live) is determined by the API key prefix
(sk_sandbox_… / sk_live_…) — no extra parameter needed.
Async usage:
import asyncio
from datetime import datetime, timedelta, timezone
from stableops import AsyncStableOps
async def main() -> None:
async with AsyncStableOps(api_key="sk_sandbox_...") as client:
order = await client.payment_orders.create(
merchant_order_id="sub_89231_2026_06",
amount="49.00",
accepted_assets=[{"chain": "base", "asset": "USDC"}],
expires_at=(datetime.now(timezone.utc) + timedelta(minutes=30)).isoformat(),
)
print(order.id)
asyncio.run(main())Payment orders
from datetime import datetime, timedelta, timezone
order = client.payment_orders.create(
merchant_order_id="sub_89231_2026_06",
amount="49.00",
accepted_assets=[
{"chain": "base", "asset": "USDC"},
{"chain": "tron", "asset": "USDT"},
],
# Auto-expire after 30 minutes; the order moves to `expired` and the address is released.
expires_at=(datetime.now(timezone.utc) + timedelta(minutes=30)).isoformat(),
# Optional: 'auto' nudges the amount to a unique value (no manual offset on SHARED addresses).
amount_mode="auto",
)
client.payment_orders.retrieve(order.id)
client.payment_orders.list(status="detected", limit=50)
client.payment_orders.cancel(order.id)merchant_order_id doubles as the idempotency key, so retries from your worker
land on the same record instead of creating duplicates.
Amounts come back as human-readable decimal strings (for example, "49.00").
Keep them as strings or use Decimal; don't reach for float(amount).
requested_amount is the base amount you submitted (for an auto order, the
pre-nudge amount, used for reconciliation).
Checkout sessions (hosted)
checkout_sessions.create returns a hosted (WalletConnect) payment page — redirect
the user to session.url.
from datetime import datetime, timedelta, timezone
session = client.checkout_sessions.create(
merchant_order_id="sub_89231_2026_06",
amount="49.00",
accepted_assets=[{"chain": "base", "asset": "USDC"}],
expires_at=(datetime.now(timezone.utc) + timedelta(minutes=30)).isoformat(),
title="Pro plan",
success_url="https://your-app.example.com/pay/success",
cancel_url="https://your-app.example.com/pay/cancel",
)
print(session.url) # send the user here to payWebhook endpoints
endpoint = client.webhooks.create_endpoint(
url="https://your-app.example.com/hooks/stableops",
enabled_events=["payment.detected", "payment.confirmed", "payment.finalized"],
# Optional: strip order metadata from the delivered payload.
redact_metadata=True,
)
endpoints = client.webhooks.list_endpoints()
client.webhooks.update_endpoint(endpoint.id, description="Production")
# endpoint.secret is only present on create/rotate. Store it somewhere durable.
client.webhooks.rotate_secret(endpoint.id)Deliveries and replay
# List deliveries (filter by status / endpoint / order).
deliveries = client.webhooks.list_deliveries(status="failed", limit=20)
# Re-deliver a specific event to a specific endpoint.
client.webhooks.replay(endpoint.id, event_id)
# Replay a single delivery.
client.webhooks.replay_delivery(delivery_id)
# Batch-replay dead letters.
result = client.webhooks.replay_dead_letters(endpoint_id=endpoint.id, limit=100)
print(result.replayed)Webhook verification
Verify the signature inside your callback handler (Flask example):
from stableops import SIGNATURE_HEADER, verify_webhook_signature
@app.route("/hooks/stableops", methods=["POST"])
def handle_webhook():
body = request.get_data(as_text=True)
result = verify_webhook_signature(
body=body,
header=request.headers.get(SIGNATURE_HEADER),
secret=os.environ["STABLEOPS_WEBHOOK_SECRET"],
)
if not result.valid:
return {"error": result.reason}, 401
# Verify against the raw body (don't re-serialize the JSON first).
...
return "", 204During key rotation, pass secrets=[old, new] to accept multiple secrets at once.
Errors
Every non-2xx response is raised as StableOpsError with .status, .code,
.message, and a raw .details body.
from datetime import datetime, timedelta, timezone
from stableops import StableOpsError
try:
client.payment_orders.create(
merchant_order_id="sub_89231_2026_06",
amount="49.00",
accepted_assets=[{"chain": "base", "asset": "USDC"}],
expires_at=(datetime.now(timezone.utc) + timedelta(minutes=30)).isoformat(),
)
except StableOpsError as err:
if err.status == 409:
# Idempotency-key was reused with a different body, etc.
...
raiseNetwork errors (timeouts / connection failures) are also wrapped as
StableOpsError, with status == 0.
How is this guide?
Last updated
TypeScript API SDK
Install and configure the StableOps TypeScript API SDK to create payment orders, manage addresses and Webhooks, and make typed server-side API calls.
Wallet SDK
Use the StableOps wallet SDK to select payment instructions, connect injected or WalletConnect wallets, send EVM, Solana, and TRON transfers, and handle errors.