Bonaventure OgetoBy Bonaventure Ogeto|

Accept Payments with Paystack in FastAPI

To accept Paystack payments in FastAPI, create async endpoints that call the Paystack API using httpx. Use Pydantic models for input validation. Initialize a transaction, return the authorization URL to your frontend, and verify the payment in a callback endpoint.

Project Setup

pip install fastapi uvicorn httpx python-decouple

Create a .env file:

PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxx
BASE_URL=http://localhost:8000
# main.py
from fastapi import FastAPI
from decouple import config

app = FastAPI()

PAYSTACK_SECRET = config('PAYSTACK_SECRET_KEY')
BASE_URL = config('BASE_URL')

Pydantic Models

# schemas.py
from pydantic import BaseModel, EmailStr

class PaymentRequest(BaseModel):
    email: EmailStr
    amount: float

class PaymentResponse(BaseModel):
    authorization_url: str
    access_code: str
    reference: str

class VerifyResponse(BaseModel):
    success: bool
    amount: float = 0
    currency: str = ''
    email: str = ''

Initialize a Transaction

# main.py
import httpx
import uuid

@app.post('/api/pay', response_model=PaymentResponse)
async def initialize_payment(payment: PaymentRequest):
    reference = 'order_' + uuid.uuid4().hex[:12]

    async with httpx.AsyncClient() as client:
        response = await client.post(
            'https://api.paystack.co/transaction/initialize',
            json={
                'email': payment.email,
                'amount': int(payment.amount * 100),
                'reference': reference,
                'callback_url': BASE_URL + '/api/payment/callback',
            },
            headers={
                'Authorization': 'Bearer ' + PAYSTACK_SECRET,
                'Content-Type': 'application/json',
            },
        )

    data = response.json()

    if not data.get('status'):
        raise HTTPException(status_code=400, detail=data.get('message', 'Failed'))

    # TODO: Save order to database

    return PaymentResponse(
        authorization_url=data['data']['authorization_url'],
        access_code=data['data']['access_code'],
        reference=data['data']['reference'],
    )

Handle the Callback

# main.py
from fastapi import Query
from fastapi.responses import RedirectResponse, HTMLResponse

@app.get('/api/payment/callback')
async def payment_callback(reference: str = Query(None), trxref: str = Query(None)):
    ref = reference or trxref

    if not ref:
        return HTMLResponse('Missing reference', status_code=400)

    async with httpx.AsyncClient() as client:
        response = await client.get(
            'https://api.paystack.co/transaction/verify/' + ref,
            headers={'Authorization': 'Bearer ' + PAYSTACK_SECRET},
        )

    data = response.json()

    if data.get('status') and data['data']['status'] == 'success':
        amount = data['data']['amount'] / 100
        # TODO: Verify amount against database, mark order as paid
        return HTMLResponse(
            'Payment successful! Amount: ' + str(amount) + ' ' + data['data']['currency']
        )

    return HTMLResponse('Payment not successful', status_code=400)

Verify Endpoint for Inline Popup

@app.get('/api/verify', response_model=VerifyResponse)
async def verify_payment(reference: str):
    async with httpx.AsyncClient() as client:
        response = await client.get(
            'https://api.paystack.co/transaction/verify/' + reference,
            headers={'Authorization': 'Bearer ' + PAYSTACK_SECRET},
        )

    data = response.json()

    if data.get('status') and data['data']['status'] == 'success':
        return VerifyResponse(
            success=True,
            amount=data['data']['amount'] / 100,
            currency=data['data']['currency'],
            email=data['data']['customer']['email'],
        )

    return VerifyResponse(success=False)

Production Checklist

  1. Use httpx connection pooling. Create a single AsyncClient instance and reuse it across requests instead of creating a new one per request.
  2. Add CORS middleware. Use FastAPI's CORSMiddleware to allow requests from your frontend domain.
  3. Set up webhooks. See Handle Paystack Webhooks in FastAPI.
  4. Use HTTPS. Required for callback and webhook URLs in production.
  5. Validate amounts. Look up the price from your database. Never trust client-submitted amounts.

Ship Payments Faster

Key Takeaways

  • FastAPI is async by default. Use httpx for non-blocking HTTP calls to the Paystack API.
  • Pydantic models validate payment input automatically. FastAPI returns 422 for invalid data.
  • Store your Paystack secret key in environment variables. Use pydantic-settings or python-decouple to load them.
  • Amounts are in kobo (NGN), pesewas (GHS), or cents (ZAR/KES/USD). Validate and convert on the server.
  • Always verify the transaction server-side. The redirect callback alone is not proof of payment.
  • FastAPI auto-generates OpenAPI docs for your payment endpoints, which helps frontend developers integrate.

Frequently Asked Questions

Why use httpx instead of requests?
The requests library is synchronous. FastAPI is async. Using requests blocks the event loop. httpx provides an async API (AsyncClient) that works naturally with FastAPI async endpoints.
Can I use the requests library with FastAPI?
Yes, but only in synchronous (non-async) endpoints using def instead of async def. FastAPI runs synchronous endpoints in a thread pool. For best performance, use httpx with async def.
Does FastAPI auto-generate docs for my payment API?
Yes. Visit /docs for Swagger UI or /redoc for ReDoc. FastAPI generates these from your Pydantic models and type annotations. This helps your frontend team understand the payment API without separate documentation.
How do I add authentication to payment endpoints?
Use FastAPI Depends with an authentication dependency. For JWT auth, decode the token in a dependency and inject the current user into your endpoint. Only authenticated users should be able to initialize payments.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon