> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# FastAPI Boilerplate

> Clone the FastAPI boilerplate to create checkout sessions, verify webhooks, and open the Customer Portal from a Python backend with the Dodo Payments SDK.

<Card title="GitHub Repository" icon="github" href="https://github.com/dodopayments/fastapi-boilerplate">
  Source code for the FastAPI and Dodo Payments boilerplate.
</Card>

## Overview

The FastAPI boilerplate is a Python backend with Dodo Payments already connected. It has endpoints that create checkout sessions and Customer Portal sessions, a webhook endpoint that verifies signatures, and a pricing page rendered from Jinja2 templates.

<Info>
  This boilerplate uses FastAPI with `async` route handlers, Pydantic for validation and settings, and the `dodopayments` Python SDK. The handlers call the synchronous `DodoPayments` client. To avoid blocking the event loop, switch to `AsyncDodoPayments` and `await` its calls.
</Info>

### Features

The boilerplate includes:

* **Quick Setup**: Go from clone to a running server in about five minutes.
* **Async Handlers**: Route handlers are FastAPI `async def` functions.
* **Checkout Sessions**: A pre-configured checkout endpoint that uses the Python SDK.
* **Webhook Handling**: A webhook endpoint that verifies each signature with the SDK's `unwrap` method.
* **Customer Portal**: An endpoint that creates Customer Portal sessions.
* **Type Safety**: Pydantic models validate request bodies, and the code uses type hints.
* **Environment Configuration**: `pydantic-settings` loads and validates the configuration from `.env`.

## Prerequisites

Before you begin, you need:

* **Python 3.9 or later**, which the `dodopayments` SDK requires. Python 3.11 or later is recommended.
* **pip** or **uv** for package management.
* **A Dodo Payments account**, to create an API key and a webhook signing secret in the dashboard.

## Quick Start

<Steps>
  <Step title="Clone the Repository">
    ```bash theme={null}
    git clone https://github.com/dodopayments/fastapi-boilerplate.git
    cd fastapi-boilerplate
    ```
  </Step>

  <Step title="Create Virtual Environment">
    Set up an isolated Python environment:

    ```bash theme={null}
    python -m venv venv
    source venv/bin/activate  # On Windows: venv\Scripts\activate
    ```

    Or use uv for faster dependency management:

    ```bash theme={null}
    uv venv
    source .venv/bin/activate
    ```
  </Step>

  <Step title="Install Dependencies">
    ```bash theme={null}
    pip install -r requirements.txt
    ```

    Or with uv:

    ```bash theme={null}
    uv pip install -r requirements.txt
    ```
  </Step>

  <Step title="Get API Credentials">
    Sign up at [Dodo Payments](https://dodopayments.com/), then get your credentials from the dashboard:

    * **API Key:** Create a key under [Dashboard → Developer → API Keys](https://app.dodopayments.com/developer/api-keys).
    * **Webhook Key:** Add an endpoint under [Dashboard → Developer → Webhooks](https://app.dodopayments.com/developer/webhooks), then copy its signing secret. The endpoint URL must be public and use HTTPS. To receive events on your machine, see [Testing Webhooks Locally](#testing-webhooks-locally).

    <Tip>
      Create both while the **Live Mode** switch in the sidebar is off. A test mode key works only with `DODO_PAYMENTS_ENVIRONMENT=test_mode`, and test mode payments don't move real money.
    </Tip>
  </Step>

  <Step title="Configure Environment Variables">
    Copy the example file to create a `.env` file in the root directory:

    ```bash theme={null}
    cp .env.example .env
    ```

    Set the values to your Dodo Payments credentials:

    ```bash .env theme={null}
    DODO_PAYMENTS_API_KEY=your_api_key_here
    DODO_PAYMENTS_WEBHOOK_KEY=your_webhook_signing_key_here
    DODO_PAYMENTS_RETURN_URL=http://localhost:8000
    DODO_PAYMENTS_ENVIRONMENT=test_mode
    ```

    All four variables are required. `app/core/config.py` loads them with `pydantic-settings`, and the app fails to start if one is missing or empty. `DODO_PAYMENTS_RETURN_URL` is where checkout sends the customer after payment.

    <Warning>
      Don't commit your `.env` file to version control. The repository's `.gitignore` already excludes it.
    </Warning>
  </Step>

  <Step title="Add Your Products">
    Replace the sample products in `app/lib/products.py` with your own. Set each `product_id` to the ID of a product under **Products** in your dashboard. The pricing page displays these products.
  </Step>

  <Step title="Run the Development Server">
    ```bash theme={null}
    uvicorn app.main:app --reload --port 8000
    ```

    Open [http://localhost:8000/docs](http://localhost:8000/docs) to see the interactive API documentation.

    <Check>
      Swagger UI lists the `/api/checkout/`, `/api/webhook/`, and `/api/customer-portal/` endpoints, ready to test.
    </Check>

    The root URL, `http://localhost:8000`, serves the pricing page.

    <Warning>
      `app/main.py` calls `templates.TemplateResponse("index.html", {"request": request, ...})`, a signature that Starlette 1.x no longer accepts, so the pricing page returns a `500` error on a fresh install. To fix it, change the call to `templates.TemplateResponse(request, "index.html", {"products": products})`.
    </Warning>
  </Step>
</Steps>

## Project Structure

```text theme={null}
fastapi-boilerplate/
├── app/
│   ├── main.py             # FastAPI application entry point
│   ├── api/
│   │   ├── checkout.py     # Checkout session endpoint
│   │   ├── portal.py       # Customer portal endpoint
│   │   └── webhook.py      # Webhook handler endpoint
│   ├── core/
│   │   └── config.py       # Environment configuration
│   ├── lib/
│   │   ├── customers.py    # Customer payload helpers
│   │   └── products.py     # Products shown on the pricing page
│   └── templates/
│       ├── base.html       # Base template
│       └── index.html      # Pricing page
├── requirements.txt        # Python dependencies
├── .env.example            # Environment template
└── README.md
```

## API Endpoints

`app/main.py` mounts each router under an `/api` prefix:

| Endpoint | Method | Description |
| - | - | - |
| `/api/checkout/` | POST | Create a new checkout session |
| `/api/webhook/` | POST | Handle Dodo Payments webhooks |
| `/api/customer-portal/` | POST | Generate customer portal URL |

Each path ends with a slash. FastAPI answers a request to the path without the slash with a `307` redirect, so use the exact path, especially in your webhook URL.

## Code Examples

These examples are condensed from the files in `app/api/`.

### Creating a Checkout Session

`app/api/checkout.py` creates a checkout session and returns its `checkout_url`. The request body takes a `product_id`, an optional `quantity`, and an optional `customer` object with `name` and `email`:

```python theme={null}
from typing import Optional

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from dodopayments import DodoPayments
from app.core.config import settings
from app.lib.customers import CustomerData, to_dodo_customer_payload

router = APIRouter()
client = DodoPayments(
    bearer_token=settings.DODO_PAYMENTS_API_KEY,
    environment=settings.DODO_PAYMENTS_ENVIRONMENT,
)

class CheckoutRequest(BaseModel):
    product_id: str
    quantity: int = 1
    customer: Optional[CustomerData] = None

@router.post("/")
async def create_checkout(request: CheckoutRequest):
    try:
        checkout_session = client.checkout_sessions.create(
            product_cart=[{
                "product_id": request.product_id,
                "quantity": request.quantity,
            }],
            customer=to_dodo_customer_payload(request.customer),
            show_saved_payment_methods=True,
            feature_flags={"allow_discount_code": True},
            return_url=settings.DODO_PAYMENTS_RETURN_URL,
        )
        return {"session_id": checkout_session.session_id, "checkout_url": checkout_session.checkout_url}
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))
```

### Handling Webhooks

`app/api/webhook.py` verifies the signature with the SDK's `unwrap` method, then branches on the event type:

```python theme={null}
import json

from fastapi import APIRouter, Request, HTTPException
from dodopayments import DodoPayments
from app.core.config import settings

router = APIRouter()
client = DodoPayments(
    bearer_token=settings.DODO_PAYMENTS_API_KEY,
    environment=settings.DODO_PAYMENTS_ENVIRONMENT,
    webhook_key=settings.DODO_PAYMENTS_WEBHOOK_KEY,
)

@router.post("/")
async def handle_webhook(request: Request):
    body = await request.body()

    # Dodo Payments follows the Standard Webhooks spec: the signature covers
    # `id.timestamp.body`, so all three webhook-* headers must be passed in.
    try:
        client.webhooks.unwrap(
            body,
            headers={
                "webhook-id": request.headers.get("webhook-id", ""),
                "webhook-signature": request.headers.get("webhook-signature", ""),
                "webhook-timestamp": request.headers.get("webhook-timestamp", ""),
            },
        )
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"Webhook verification failed: {e}")

    event_type = json.loads(body).get("type", "")

    if event_type == "subscription.active":
        print("Subscription is active")  # Grant access, update DB, send email
    elif event_type == "payment.succeeded":
        print("Payment succeeded")  # Fulfill order, update DB, send email
    elif event_type == "subscription.cancelled":
        print("Subscription cancelled")  # Revoke access, update user status
    elif event_type == "payment.failed":
        print("Payment failed")  # Notify user, update payment status
    else:
        print(f"Unhandled event type: {event_type}")

    return {"status": "success", "event": event_type}
```

### Customer Portal Integration

`app/api/portal.py` creates a Customer Portal session for a customer ID and returns the portal link as `url`:

```python theme={null}
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from dodopayments import DodoPayments
from app.core.config import settings
from app.lib.customers import sanitize_customer_id

router = APIRouter()
client = DodoPayments(
    bearer_token=settings.DODO_PAYMENTS_API_KEY,
    environment=settings.DODO_PAYMENTS_ENVIRONMENT,
)

class PortalRequest(BaseModel):
    customer_id: str

@router.post("/")
async def create_customer_portal(request: PortalRequest):
    try:
        customer_id = sanitize_customer_id(request.customer_id)
        portal_session = client.customers.customer_portal.create(
            customer_id=customer_id,
        )
        return {"url": portal_session.link}
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))
```

The pricing page in `app/templates/index.html` sends a hardcoded customer ID (`cus_001`) to this endpoint, and a hardcoded name and email to the checkout endpoint. Replace them with the signed-in user's values.

## Webhook Events

The handler in `app/api/webhook.py` branches on these events:

| Event | Description |
| - | - |
| `payment.succeeded` | Payment completed successfully |
| `payment.failed` | Payment attempt failed |
| `subscription.active` | Subscription is now active |
| `subscription.cancelled` | Subscription was cancelled |

To handle another event, add a branch for its type, such as `refund.succeeded` for a refund that processed successfully. For every event type, see the [Webhook Event Guide](/developer-resources/webhooks/intents/webhook-events-guide).

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

Dodo Payments can't reach `localhost`. For local development, use a tool such as [ngrok](https://ngrok.com/) to expose your local server:

```bash theme={null}
ngrok http 8000
```

Add the ngrok HTTPS URL, followed by `/api/webhook/`, as an endpoint in your [Dodo Payments Dashboard](https://app.dodopayments.com/developer/webhooks):

```text theme={null}
https://your-ngrok-url.ngrok.io/api/webhook/
```

Copy the endpoint's signing secret into `DODO_PAYMENTS_WEBHOOK_KEY` in `.env`, then restart the server. The app reads `.env` only at startup.

## Deployment

### Docker

The repository doesn't include a `Dockerfile`. To run the app in a container, add this `Dockerfile` to the repository root:

```dockerfile theme={null}
FROM python:3.11-slim

WORKDIR /app

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

COPY . .

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
```

`COPY . .` copies every file in the build context, including `.env`. To keep your keys out of the image, add a `.dockerignore` file that lists `.env`. Then build the image and run it with your environment file:

```bash theme={null}
docker build -t fastapi-dodo .
docker run -p 8000:8000 --env-file .env fastapi-dodo
```

### Production Considerations

<Warning>
  Before deploying to production:

  * Switch `DODO_PAYMENTS_ENVIRONMENT` to `live_mode`.
  * Use a live mode API key from the dashboard.
  * Add a webhook endpoint for your production domain, and set `DODO_PAYMENTS_WEBHOOK_KEY` to its signing secret.
  * Set `DODO_PAYMENTS_RETURN_URL` to your production URL.
  * Enable HTTPS for all endpoints.
</Warning>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Import errors or missing modules">
    Make sure your virtual environment is activated and dependencies are installed:

    ```bash theme={null}
    source venv/bin/activate
    pip install -r requirements.txt
    ```
  </Accordion>

  <Accordion title="Server fails to start with Directory 'app/static' does not exist">
    `app/main.py` serves static files from `app/static`, but the repository doesn't include that directory. Create it with `mkdir app/static`, then start the server again.
  </Accordion>

  <Accordion title="Checkout session creation fails">
    Check for these common causes:

    * The product ID doesn't exist in your Dodo Payments dashboard.
    * The API key or `DODO_PAYMENTS_ENVIRONMENT` in `.env` is wrong. A test mode key works only with `test_mode`.

    The endpoint returns the SDK error in a `400` response. Check the FastAPI logs for detailed error messages.
  </Accordion>

  <Accordion title="Webhooks not receiving events">
    For local testing, use [ngrok](https://ngrok.com) to expose your server:

    ```bash theme={null}
    ngrok http 8000
    ```

    In your [Dodo dashboard](https://app.dodopayments.com/developer/webhooks), add an endpoint with the ngrok URL followed by `/api/webhook/`, including the trailing slash. Copy that endpoint's signing secret into `DODO_PAYMENTS_WEBHOOK_KEY` in your `.env` file.
  </Accordion>

  <Accordion title="Webhook signature verification fails">
    * Make sure `DODO_PAYMENTS_WEBHOOK_KEY` in `.env` matches the endpoint's signing secret.
    * Verify the signature against the raw request body, before you parse it as JSON.
    * Pass all three `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers to `client.webhooks.unwrap()`. The Standard Webhooks signature covers `id.timestamp.body`, not the body alone.
  </Accordion>
</AccordionGroup>

## Learn More

<CardGroup cols={2}>
  <Card title="Python SDK" icon="python" href="/developer-resources/sdks/python">
    Complete Python SDK documentation with async support
  </Card>

  <Card title="Webhooks Documentation" icon="webhook" href="/developer-resources/webhooks">
    Learn about all webhook events and best practices
  </Card>

  <Card title="Checkout Sessions" icon="credit-card" href="/developer-resources/checkout-session">
    Deep dive into checkout session configuration
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/introduction">
    Complete Dodo Payments API documentation
  </Card>
</CardGroup>

## Support

For help with the boilerplate:

* Ask questions in the [Discord community](https://discord.gg/bYqAp4ayYh).
* Report issues and follow updates in the [GitHub repository](https://github.com/dodopayments/fastapi-boilerplate).
* Email the [support team](mailto:support@dodopayments.com).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.