Docs / Patterns / FastAPI
FastAPI.
Server route that creates an invoice and redirects to hosted checkout, and a webhook handler that verifies the signature and reacts to invoice.paid. Both files are paste-and-go.
Install.
Env vars.
Set these on your server. SETTLE_API_KEY is your sk_test_… or sk_live_… key from the dashboard. SETTLE_WEBHOOK_SECRET is returned by register_webhook when you set up the endpoint.
SETTLE_API_KEYSETTLE_WEBHOOK_SECRET
Server route.
Receives a POST from your frontend with the order amount and customer email, creates an invoice via the REST API, and 303-redirects the customer to the hosted checkout URL.
# app/checkout.py
import os
import httpx
from fastapi import APIRouter, Request
from fastapi.responses import RedirectResponse, JSONResponse
router = APIRouter()
@router.post("/api/checkout")
async def create_checkout(request: Request):
body = await request.json()
origin = str(request.base_url).rstrip("/")
async with httpx.AsyncClient(timeout=10.0) as client:
res = await client.post(
"https://api.settle.xxx/v1/invoices",
headers={"Authorization": f"Bearer {os.environ['SETTLE_API_KEY']}"},
json={
"amount_usd": body["amount_usd"],
"customer_email": body.get("customer_email"),
"metadata": body.get("metadata"),
"return_url": f"{origin}/orders/success",
"cancel_url": f"{origin}/cart",
},
)
if res.status_code >= 400:
return JSONResponse({"error": "checkout_failed"}, status_code=502)
invoice = res.json()
return RedirectResponse(invoice["checkout_url"], status_code=303)
Webhook handler.
Verifies the HMAC-SHA256 signature on the raw request body, then handles invoice.paid. Read Webhooks for the event list and retry semantics.
# app/webhooks.py
import hmac
import hashlib
import os
from fastapi import APIRouter, Request, HTTPException
router = APIRouter()
@router.post("/webhooks/settle")
async def settle_webhook(request: Request):
signature = request.headers.get("settle-signature")
if not signature:
raise HTTPException(status_code=400, detail="missing signature")
raw = await request.body()
secret = os.environ["SETTLE_WEBHOOK_SECRET"].encode()
expected = hmac.new(secret, raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
raise HTTPException(status_code=401, detail="bad signature")
event = await request.json()
if event["type"] == "invoice.paid":
# Mark order paid.
pass
return {"received": True}
Notes.
Use hmac.compare_digest for the signature check — never == on bytes. Read the raw body with await request.body() before any json parsing, otherwise FastAPI's middleware can mutate it.