Skip to main content

GitHub Repository

Complete FastAPI + Dodo Payments boilerplate

Overview

The FastAPI boilerplate provides a production-ready starting point for integrating Dodo Payments with your Python backend. This template includes checkout session handling, webhook verification, customer portal integration, and async API patterns to help you start accepting payments quickly.
This boilerplate uses FastAPI with async/await patterns, Pydantic for validation, and the dodopayments Python SDK for seamless API integration.

Features

  • Quick Setup - Get started in under 5 minutes
  • Async Support - Built with FastAPI’s native async/await patterns
  • Checkout Sessions - Pre-configured checkout flow using the Python SDK
  • Webhook Handling - Secure webhook endpoint with signature verification
  • Customer Portal - Easy customer portal session creation
  • Type Safety - Full Pydantic validation and type hints
  • Environment Configuration - Ready-to-use environment variable setup

Prerequisites

Before you begin, make sure you have:
  • Python 3.9+ (recommended: Python 3.11+)
  • pip or uv for package management
  • Dodo Payments account (to access API and Webhook Keys from dashboard)

Quick Start

1

Clone the Repository

git clone https://github.com/dodopayments/fastapi-boilerplate.git
cd fastapi-boilerplate
2

Create Virtual Environment

Set up an isolated Python environment:
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
Or using uv for faster dependency management:
uv venv
source .venv/bin/activate
3

Install Dependencies

pip install -r requirements.txt
Or with uv:
uv pip install -r requirements.txt
4

Get API Credentials

Sign up at Dodo Payments and get your credentials from the dashboard:
Make sure you’re in Test Mode while developing!
5

Configure Environment Variables

Create a .env file in the root directory:
cp .env.example .env
Update the values with your Dodo Payments credentials:
.env
DODO_PAYMENTS_API_KEY=your_api_key_here
DODO_PAYMENTS_WEBHOOK_KEY=your_webhook_signing_key_here
DODO_PAYMENTS_ENVIRONMENT=test_mode
Never commit your .env file to version control. It’s already included in .gitignore.
6

Run the Development Server

uvicorn main:app --reload
Open http://localhost:8000/docs to see the interactive API documentation!
You should see FastAPI’s Swagger UI with all available endpoints ready to test.

Project Structure

fastapi-boilerplate/
├── main.py                 # FastAPI application entry point
├── routers/
│   ├── checkout.py         # Checkout session endpoints
│   ├── webhook.py          # Webhook handler endpoint
│   └── customer_portal.py  # Customer portal endpoints
├── config.py               # Environment configuration
├── requirements.txt        # Python dependencies
├── .env.example            # Environment template
└── README.md

API Endpoints

The boilerplate includes the following pre-configured endpoints:
EndpointMethodDescription
/checkoutPOSTCreate a new checkout session
/webhookPOSTHandle Dodo Payments webhooks
/customer-portalPOSTGenerate customer portal URL

Code Examples

Creating a Checkout Session

from fastapi import APIRouter, HTTPException
from dodopayments import AsyncDodoPayments
from pydantic import BaseModel
from config import settings

router = APIRouter()
dodo = AsyncDodoPayments(
    bearer_token=settings.DODO_PAYMENTS_API_KEY,
    environment=settings.DODO_PAYMENTS_ENVIRONMENT
)

class CheckoutRequest(BaseModel):
    product_id: str
    quantity: int = 1
    customer_email: str | None = None
    customer_name: str | None = None

@router.post("/checkout")
async def create_checkout(request: CheckoutRequest):
    try:
        session = await dodo.checkout_sessions.create(
            product_cart=[{
                "product_id": request.product_id,
                "quantity": request.quantity
            }],
            customer={
                "email": request.customer_email,
                "name": request.customer_name
            } if request.customer_email else None,
            return_url="http://localhost:8000/success"
        )
        return {"checkout_url": session.url, "session_id": session.session_id}
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

Handling Webhooks

from fastapi import APIRouter, Request, HTTPException
import hmac
import hashlib
from config import settings

router = APIRouter()

def verify_webhook_signature(payload: bytes, signature: str) -> bool:
    expected = hmac.new(
        settings.DODO_PAYMENTS_WEBHOOK_KEY.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

@router.post("/webhook")
async def handle_webhook(request: Request):
    payload = await request.body()
    signature = request.headers.get("webhook-signature", "")
    
    if not verify_webhook_signature(payload, signature):
        raise HTTPException(status_code=401, detail="Invalid signature")
    
    event = await request.json()
    event_type = event.get("type")
    
    match event_type:
        case "payment.succeeded":
            # Handle successful payment
            payment_id = event["data"]["payment_id"]
            print(f"Payment succeeded: {payment_id}")
            
        case "subscription.active":
            # Handle subscription activation
            subscription_id = event["data"]["subscription_id"]
            print(f"Subscription activated: {subscription_id}")
            
        case "refund.succeeded":
            # Handle refund
            refund_id = event["data"]["refund_id"]
            print(f"Refund processed: {refund_id}")
    
    return {"status": "received"}

Customer Portal Integration

from fastapi import APIRouter, HTTPException
from dodopayments import AsyncDodoPayments
from pydantic import BaseModel
from config import settings

router = APIRouter()
dodo = AsyncDodoPayments(
    bearer_token=settings.DODO_PAYMENTS_API_KEY,
    environment=settings.DODO_PAYMENTS_ENVIRONMENT
)

class PortalRequest(BaseModel):
    customer_id: str

@router.post("/customer-portal")
async def create_portal_session(request: PortalRequest):
    try:
        session = await dodo.customers.create_customer_portal(
            customer_id=request.customer_id
        )
        return {"portal_url": session.url}
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

Webhook Events

The boilerplate demonstrates handling common webhook events:
EventDescription
payment.succeededPayment completed successfully
payment.failedPayment attempt failed
subscription.activeSubscription is now active
subscription.cancelledSubscription was cancelled
refund.succeededRefund processed successfully
Add your business logic inside the webhook handler to:
  • Update user permissions in your database
  • Send confirmation emails
  • Provision access to digital products
  • Track analytics and metrics

Testing Webhooks Locally

For local development, use tools like ngrok to expose your local server:
ngrok http 8000
Update the webhook URL in your Dodo Payments Dashboard:
https://your-ngrok-url.ngrok.io/webhook

Deployment

Docker

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Build and run:
docker build -t fastapi-dodo .
docker run -p 8000:8000 --env-file .env fastapi-dodo

Production Considerations

Before deploying to production:
  • Switch DODO_PAYMENTS_ENVIRONMENT to live_mode
  • Use production API keys from the dashboard
  • Update the webhook URL to your production domain
  • Enable HTTPS for all endpoints

Troubleshooting

Ensure your virtual environment is activated and dependencies are installed:
source venv/bin/activate
pip install -r requirements.txt
Common causes:
  • Invalid product ID - verify it exists in your Dodo dashboard
  • Wrong API key or environment setting in .env
  • Check the FastAPI logs for detailed error messages
For local testing, use ngrok to expose your server:
ngrok http 8000
Update the webhook URL in your Dodo dashboard to the ngrok URL. Make sure to update your .env file with the correct webhook verification key.
  • Ensure DODO_PAYMENTS_WEBHOOK_KEY is correctly set in your .env
  • Verify you’re using the raw request body for signature verification
  • Check that you’re reading the webhook-signature header correctly

Learn More

Support

Need help with the boilerplate?