# How to Accept Payments in a FastAPI Backend

> Learn how to integrate Dodo Payments into your FastAPI backend with async webhook handlers, Pydantic validation, and secure signature verification.
- **Author**: Ayush Agarwal
- **Published**: 2026-03-29
- **Category**: Payments, Developer Tools, How-To
- **URL**: https://dodopayments.com/blogs/accept-payments-fastapi

---

FastAPI has become the go-to framework for Python developers who need high performance and modern features like type hints and async support. When you are building a SaaS or a digital product, the most critical part of your backend is the payment integration. You need a system that is not only fast but also secure and easy to maintain.

In this guide, we will walk through the process of integrating Dodo Payments into a FastAPI application. We will cover everything from setting up the environment to handling asynchronous webhooks and validating payloads with Pydantic. By the end of this post, you will have a production-ready payment system that handles global taxes and compliance automatically.

## Why FastAPI for Payments?

FastAPI is built on top of Starlette and Pydantic, making it incredibly fast and reliable. Its native support for asynchronous programming is a perfect match for payment processing, where you often need to wait for external API calls or database updates. The automatic documentation generation with Swagger UI also makes testing your payment endpoints a breeze.

> A payment integration should take hours, not weeks. If your developers are reading documentation for days before writing the first line of code, the platform is not developer-first. It is developer-hostile.
>
> \- Ayush Agarwal, Co-founder & CPTO at Dodo Payments

When you combine FastAPI with Dodo Payments, you get a powerful stack. Dodo Payments acts as a Merchant of Record (MoR), which means it handles all the messy parts of global commerce like VAT collection, tax remittance, and compliance. You focus on your code, and Dodo handles the rest.

## Setting Up Your FastAPI Project

Before we dive into the code, let's set up a clean environment. We recommend using Python 3.9 or higher. You can use `pip` or `uv` to manage your dependencies. For this project, we will need `fastapi`, `uvicorn`, and the `dodopayments` Python SDK.

```bash
pip install fastapi uvicorn dodopayments python-dotenv
```

Create a `.env` file in your project root to store your API keys. You can find these in your Dodo Payments dashboard. Never commit this file to version control.

```env
DODO_PAYMENTS_API_KEY=your_api_key_here
DODO_PAYMENTS_WEBHOOK_KEY=your_webhook_key_here
DODO_PAYMENTS_ENVIRONMENT=test_mode
```

## Initializing the Dodo Payments Client

The Dodo Payments Python SDK provides both synchronous and asynchronous clients. Since we are using FastAPI, we will use the `AsyncDodoPayments` client to keep our application non-blocking.

```python
import os
from dotenv import load_dotenv
from dodopayments import AsyncDodoPayments

load_dotenv()

client = AsyncDodoPayments(
    bearer_token=os.getenv("DODO_PAYMENTS_API_KEY"),
    environment=os.getenv("DODO_PAYMENTS_ENVIRONMENT")
)
```

This client will be used to create checkout sessions and interact with the Dodo Payments API. Using the async client ensures that your server can handle other requests while waiting for a response from the payment gateway.

## Creating a Checkout Session

The first step in any payment flow is creating a checkout session. This session generates a secure URL where your customers can enter their payment details. In FastAPI, we can create a simple POST endpoint that takes a product ID and returns the checkout URL.

```python
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel

router = APIRouter()

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

@router.post("/checkout")
async def create_checkout(request: CheckoutRequest):
    try:
        session = await client.checkout_sessions.create(
            product_cart=[{
                "product_id": request.product_id,
                "quantity": request.quantity
            }],
            customer={
                "email": request.customer_email,
                "name": request.customer_name
            },
            return_url="https://dodopayments.com/success"
        )
        return {"checkout_url": session.checkout_url, "session_id": session.session_id}
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))
```

This endpoint uses Pydantic for request validation, ensuring that the incoming data is correctly formatted. The `checkout_sessions.create` method returns a session object containing the URL you should redirect your user to.
## Deep Dive into Pydantic Validation

One of the standout features of FastAPI is its integration with Pydantic. When you define a `CheckoutRequest` model, FastAPI automatically validates the incoming JSON payload against that model. This means you don't have to write manual validation logic for every field.

```python
from pydantic import EmailStr, Field

class CheckoutRequest(BaseModel):
    product_id: str = Field(..., description="The unique ID of the product")
    quantity: int = Field(1, ge=1, le=100, description="Quantity must be between 1 and 100")
    customer_email: EmailStr = Field(..., description="A valid email address")
    customer_name: str = Field(..., min_length=2, max_length=100)
```

By using `EmailStr`, Pydantic ensures that the `customer_email` field is a valid email format. The `Field` function allows you to add constraints like `ge` (greater than or equal to) and `le` (less than or equal to), as well as descriptions that will appear in your auto-generated API documentation.

If a user sends an invalid request, FastAPI will automatically return a 422 Unprocessable Entity error with a detailed explanation of what went wrong. This saves you a significant amount of time and reduces the surface area for bugs in your payment flow.

## Integrating with a Database

In a real-world application, you need to store payment information in a database. Whether you are using SQLAlchemy, SQLModel, or Tortoise ORM, the pattern remains the same: update your records when a webhook confirms a successful payment.

Let's look at an example using SQLModel, which is a library built on top of SQLAlchemy and Pydantic, designed specifically for FastAPI.

```python
from sqlmodel import SQLModel, Field, create_engine, Session, select

class User(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    email: str = Field(index=True)
    is_premium: bool = Field(default=False)
    subscription_id: str | None = None

sqlite_url = "sqlite:///database.db"
engine = create_engine(sqlite_url)

def update_user_subscription(email: str, subscription_id: str):
    with Session(engine) as session:
        statement = select(User).where(User.email == email)
        user = session.exec(statement).first()
        if user:
            user.is_premium = True
            user.subscription_id = subscription_id
            session.add(user)
            session.commit()
```

In your webhook handler, you would call `update_user_subscription` when you receive a `subscription.active` event. This ensures that your application's state is always in sync with the actual payment status.

## Robust Error Handling and Logging

Payment processing is a high-stakes operation. If something goes wrong, you need to know exactly what happened and why. This is where structured logging and robust error handling come in.

FastAPI provides a powerful exception handling system. You can define custom exception handlers that catch specific errors and return formatted JSON responses.

```python
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@router.post("/webhook")
async def handle_webhook(request: Request):
    # ... verification logic ...
    try:
        # ... processing logic ...
        logger.info(f"Successfully processed event: {event.type}")
    except Exception as e:
        logger.error(f"Error processing webhook: {str(e)}", exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")
```

By logging the event type and any errors that occur, you create an audit trail that can be used to troubleshoot issues. Using `exc_info=True` in your logger will include the full stack trace, making it much easier to find the root cause of a failure.

## Testing with Pytest and HTTPX

Testing your payment endpoints is non-negotiable. You should have unit tests for your validation logic and integration tests for your API endpoints. FastAPI's `TestClient` (which uses `httpx` under the hood) makes this straightforward.

```python
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_create_checkout_invalid_email():
    response = client.post(
        "/checkout",
        json={
            "product_id": "pdt_123",
            "quantity": 1,
            "customer_email": "not-an-email",
            "customer_name": "John Doe"
        }
    )
    assert response.status_code == 422
    assert "customer_email" in response.json()["detail"][0]["loc"]
```

For testing webhooks, you can mock the signature verification or use a test webhook key to generate valid signatures in your test suite. This allows you to verify that your event routing logic works correctly without needing to send real requests from Dodo Payments.

## Deployment Considerations

When you are ready to deploy your FastAPI application, there are a few things to keep in mind:

- **Use a Production Server**: Use `gunicorn` with the `uvicorn` worker class for better performance and stability in production.
- **HTTPS is Mandatory**: Never process payments over unencrypted HTTP. Ensure your production environment uses SSL/TLS.
- **Secrets Management**: Use a service like AWS Secrets Manager, HashiCorp Vault, or your platform's built-in secrets management (like GitHub Actions secrets or Vercel environment variables) to store your Dodo Payments keys.
- **Monitoring**: Implement health checks and monitoring to ensure your payment endpoints are always available.

## Handling Webhooks Securely

Webhooks are essential for keeping your database in sync with payment events. When a payment succeeds, Dodo Payments sends a POST request to your server. It is crucial to verify the signature of these requests to ensure they actually came from Dodo.

FastAPI makes it easy to handle raw request bodies, which is required for signature verification. We can use the `client.webhooks.unwrap` method provided by the SDK to verify and parse the incoming event.

```python
from fastapi import Request, Header

@router.post("/webhook")
async def handle_webhook(
    request: Request,
    webhook_id: str = Header(None),
    webhook_signature: str = Header(None),
    webhook_timestamp: str = Header(None)
):
    payload = await request.body()

    try:
        event = client.webhooks.unwrap(
            payload,
            headers={
                "webhook-id": webhook_id,
                "webhook-signature": webhook_signature,
                "webhook-timestamp": webhook_timestamp,
            }
        )
    except Exception:
        raise HTTPException(status_code=401, detail="Invalid signature")

    # Process the event
    await process_event(event)
    return {"status": "received"}
```

By using FastAPI's `Header` dependency, we can cleanly extract the necessary metadata for verification. If the signature is invalid, we return a 401 Unauthorized status code.

## Processing Webhook Events

Once the event is verified, you need to handle the specific event type. Common events include `payment.succeeded`, `subscription.active`, and `refund.succeeded`. You can use Python's `match` statement for clean event routing.

```python
async def process_event(event):
    event_type = event.type
    data = event.data

    match event_type:
        case "payment.succeeded":
            payment_id = data.payment_id
            customer_email = data.customer.email
            # Update your database, grant access, etc.
            print(f"Payment {payment_id} succeeded for {customer_email}")

        case "subscription.active":
            subscription_id = data.subscription_id
            # Activate user subscription
            print(f"Subscription {subscription_id} is now active")

        case "payment.failed":
            # Handle failed payment, maybe send an email
            print(f"Payment failed for {data.customer.email}")
```

This modular approach keeps your webhook handler clean and easy to test. You can add more cases as your application grows, such as handling disputes or license key creation.

## Payment Architecture Overview

Understanding the flow of data between your FastAPI backend, the user's browser, and Dodo Payments is key to a successful integration. The following diagram illustrates the typical payment lifecycle.

```mermaid
flowchart TD
    A[User clicks Buy] --> B[FastAPI: Create Checkout Session]
    B --> C[Redirect User to Dodo Checkout]
    C --> D[User Completes Payment]
    D --> E[Dodo: Process Transaction & Tax]
    E --> F[Dodo: Send Webhook to FastAPI]
    F --> G[FastAPI: Verify Signature & Update DB]
    G --> H[User Redirected to Success Page]
```

This architecture ensures that your backend is always the source of truth for payment status, while Dodo Payments handles the heavy lifting of transaction processing and compliance.

## Advanced Integration: Dependency Injection

FastAPI's dependency injection system is one of its most powerful features. You can use it to create a reusable dependency that checks if a user has an active subscription before allowing access to certain routes.

```python
from fastapi import Depends

async def get_current_user_subscription(user_id: str):
    # Fetch subscription status from your database
    # This status was updated by your webhook handler
    subscription = await db.get_subscription(user_id)
    if not subscription or subscription.status != "active":
        raise HTTPException(status_code=403, detail="Active subscription required")
    return subscription

@router.get("/premium-content")
async def get_premium_data(subscription = Depends(get_current_user_subscription)):
    return {"data": "This is premium content"}
```

This pattern keeps your route handlers clean and ensures that access control is consistent across your entire API.

## Testing Your Integration

Testing is a critical part of any payment integration. Dodo Payments provides a test mode that allows you to simulate successful and failed payments without using real money. You can use tools like `pytest` and `httpx` to test your FastAPI endpoints.

For local webhook testing, we recommend using `ngrok` to expose your local server to the internet. This allows Dodo Payments to send webhook requests directly to your development machine.

```bash
ngrok http 8000
```

Once ngrok is running, update your webhook URL in the Dodo Payments dashboard to point to your ngrok URL. You can then use the "Test Webhook" feature in the dashboard to send sample events to your FastAPI backend.

## Best Practices for FastAPI Payments

When building a payment system with FastAPI, keep these best practices in mind:

- **Use Async Everywhere**: Ensure your database drivers and external API calls are all asynchronous to prevent blocking the event loop.
- **Validate Everything**: Use Pydantic models for both incoming requests and outgoing responses. This provides automatic documentation and runtime validation.
- **Log Everything**: Keep detailed logs of all webhook events and API responses. This is invaluable for debugging payment issues.
- **Idempotency**: Ensure your webhook handlers are idempotent. If Dodo Payments sends the same event twice, your system should handle it gracefully without duplicating actions.
- **Secure Your Keys**: Use environment variables or a secrets manager to store your API keys. Never hardcode them in your source code.

## Conclusion

Integrating payments into a FastAPI backend doesn't have to be complicated. By using Dodo Payments as your Merchant of Record, you can offload the complexities of global tax and compliance while maintaining full control over your user experience. With FastAPI's modern features and the Dodo Payments Python SDK, you can build a robust, high-performance payment system in record time.

Whether you are building a simple digital product or a complex B2B SaaS, the combination of FastAPI and Dodo Payments provides the scalability and reliability you need to grow your business globally.

For more information on building with Dodo Payments, check out our guides on [how to accept online payments](https://dodopayments.com/blogs/how-to-accept-online-payments) and [API monetization](https://dodopayments.com/blogs/api-monetization). You can also explore our [payments architecture for SaaS](https://dodopayments.com/blogs/payments-architecture-saas) to learn more about building scalable billing systems.

If you are looking for more technical details, the [Dodo Payments documentation](https://docs.dodopayments.com) is a great place to start. You can find detailed information on [webhooks](https://docs.dodopayments.com/developer-resources/webhooks), [Python SDKs](https://docs.dodopayments.com/developer-resources/dodo-payments-sdks), and our [integration guide](https://docs.dodopayments.com/developer-resources/integration-guide).

## FAQ

### How do I handle recurring payments in FastAPI?

You can handle recurring payments by listening for the `subscription.active` and `subscription.renewed` webhook events. When these events occur, update the user's subscription status in your database to ensure they continue to have access to your service.

### Is the Dodo Payments Python SDK async-compatible?

Yes, the Dodo Payments Python SDK provides an `AsyncDodoPayments` client specifically designed for use with asynchronous frameworks like FastAPI. This allows you to make non-blocking API calls and maintain high performance.

### How do I verify webhook signatures in FastAPI?

You can verify webhook signatures using the `client.webhooks.unwrap` method. This method takes the raw request body and the headers provided by Dodo Payments to ensure the request is authentic and has not been tampered with.

### Can I use FastAPI with Dodo Payments for global sales?

Absolutely. Dodo Payments acts as a Merchant of Record, handling all global tax collection and compliance for you. This means you can sell to customers in over 220+ countries and regions without worrying about local tax laws or regulations.

### What happens if a payment fails?

If a payment fails, Dodo Payments will send a `payment.failed` webhook event to your server. You can use this event to notify the user, perhaps by sending an automated email with a link to update their payment method.

## Final Thoughts

Building a payment system is a journey, not a destination. As your business grows, you may need to implement more complex features like [usage-based billing](https://dodopayments.com/blogs/implement-usage-based-billing) or [merchant of record for SaaS](https://dodopayments.com/blogs/merchant-of-record-for-saas). By starting with a solid foundation like FastAPI and Dodo Payments, you are well-positioned to handle whatever challenges come your way.

Ready to start accepting payments? [Sign up for Dodo Payments](https://dodopayments.com) today and get your FastAPI backend up and running in minutes.