StableOps
Guides

FastAPI Integration

Build a StableOps payment integration with FastAPI and the Python SDK, using sandbox orders, wallet instructions, and verified Webhooks for safe fulfillment.

Install

pip install stableops fastapi uvicorn python-dotenv

Configure

STABLEOPS_API_KEY=sk_sandbox_...
STABLEOPS_WEBHOOK_SECRET=whsec_...

Create lib/stableops.py:

import os

from stableops import StableOps

stableops = StableOps(
    api_key=os.environ["STABLEOPS_API_KEY"],
)

The environment (sandbox / live) is determined by the API key prefix (sk_sandbox_… / sk_live_…) — no extra parameter needed.

This guide uses a sandbox key and Base Sepolia. Import a base-sepolia receiving address in Sandbox and prepare test USDC first. For production, switch the API key, accepted mainnet assets, and Live address pool together; changing only the key is not enough.

These snippets omit business authentication. In production, authenticate the user, check order ownership, and load the payable amount from trusted server-side product or invoice records rather than accepting a client-supplied price. Authorize order retrieval and cancellation as well.

Create payment orders

from datetime import datetime, timedelta, timezone

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel

from lib.stableops import stableops

router = APIRouter()

class CreateOrderRequest(BaseModel):
    merchant_order_id: str
    amount: str

@router.post("/orders")
def create_order(input: CreateOrderRequest):
    try:
        # 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()
        order = stableops.payment_orders.create(
            merchant_order_id=input.merchant_order_id,
            amount=input.amount,
            accepted_assets=[{"chain": "base-sepolia", "asset": "USDC"}],
            expires_at=expires_at,
        )
        return {
            "id": order.id,
            "status": order.status,
            "amount": order.amount,
            "payment_instructions": [
                instruction.model_dump() for instruction in order.payment_instructions
            ],
        }
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc

The Python SDK sends the current payment-order payload and reuses merchant_order_id as the Idempotency-Key header.

Verify webhooks

import json
import os

from fastapi import APIRouter, Header, Request, Response
from stableops.webhooks import SIGNATURE_HEADER, verify_webhook_signature

router = APIRouter()

@router.post("/webhooks/stableops")
async def stableops_webhook(
    request: Request,
    x_product_signature: str | None = Header(default=None, alias=SIGNATURE_HEADER),
):
    raw_body = await request.body()
    result = verify_webhook_signature(
        body=raw_body,
        header=x_product_signature,
        secret=os.environ["STABLEOPS_WEBHOOK_SECRET"],
    )

    if not result.valid:
        return Response(f"invalid signature: {result.reason}", status_code=400)

    event = json.loads(raw_body)
    if event["type"] == "payment.finalized":
        payment_order_id = event["data"]["payment_order_id"]
        # Fulfill after deduping X-Event-Id in your database.

    return Response("ok")

How is this guide?

Last updated

On this page