Payment Docs
Code examples

Python

Minimal FastAPI server that creates orders and verifies webhooks

A minimal FastAPI server with two routes: one creates an order and redirects the payer, the other receives webhooks and verifies the X-Signature header.

pip install fastapi uvicorn requests
main.py
import hashlib
import hmac
import os

import requests
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse, RedirectResponse

API_URL = "https://api.riopay.online/v1"
API_TOKEN = os.environ["API_TOKEN"]  # issued by your manager

app = FastAPI(title="Payments example")


# 1. Create an order and redirect the payer to the payment page
@app.post("/create-order")
def create_order(amount: float, order_id: str, user_id: str | None = None):
    payload = {
        "amount": str(amount),
        "externalId": order_id,
        "externalUserId": user_id,
        "purpose": f"Payment for order {order_id}",
        "successUrl": "https://your-site.com/payment-success",
        "failUrl": "https://your-site.com/payment-failed",
        "callbackUrl": "https://your-site.com/webhooks/payments",
    }
    headers = {"X-Api-Token": API_TOKEN, "Content-Type": "application/json"}

    response = requests.post(f"{API_URL}/orders", json=payload, headers=headers, timeout=30)
    if response.status_code != 200:
        raise HTTPException(status_code=response.status_code, detail=response.text)

    order = response.json()
    # Store order["id"] next to your own order_id to match webhooks later
    return RedirectResponse(url=order["paymentLink"])


# 2. Receive webhooks
@app.post("/webhooks/payments")
async def webhook(request: Request):
    raw_body = await request.body()
    signature = request.headers.get("x-signature", "")

    expected = hmac.new(API_TOKEN.encode(), raw_body, hashlib.sha512).hexdigest()
    if not hmac.compare_digest(expected, signature):
        raise HTTPException(status_code=403, detail="Invalid signature")

    data = await request.json()
    status, external_id = data.get("status"), data.get("externalId")

    # Webhooks may arrive more than once: make this idempotent
    if status == "COMPLETED":
        print(f"Order {external_id} paid")
        # mark the order as paid in your database
    elif status in {"REFUND", "CHARGEBACK"}:
        print(f"Order {external_id} reversed: {status}")
        # the money went back to the payer: mark the order as refunded
    elif status in {"FAILED", "CANCELED", "EXPIRED", "BLOCKED"}:
        print(f"Order {external_id} finished with {status}")
        # may still become COMPLETED later if the bank confirms the payment

    return JSONResponse(content={"message": "OK"})

Run it with:

uvicorn main:app --port 3000

What to adapt

  • Replace the hard-coded URLs with your own success, fail and webhook addresses.
  • Persist the id from the order response together with your externalId.
  • Move status handling into your order model; only COMPLETED means the money arrived.

On this page