> ## 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.

# Plantilla de FastAPI

> Clona el boilerplate de FastAPI para crear sesiones de checkout, verificar webhooks y abrir el Customer Portal desde un backend de Python con el SDK de Dodo Payments.

<Card title="GitHub Repository" icon="github" href="https://github.com/dodopayments/fastapi-boilerplate">
  Código fuente del boilerplate de FastAPI y Dodo Payments.
</Card>

## Descripción general

El boilerplate de FastAPI es un backend de Python con Dodo Payments ya conectado. Incluye endpoints que crean sesiones de checkout y sesiones del Customer Portal, un endpoint de webhook que verifica firmas y una página de precios renderizada con plantillas de Jinja2.

<Info>
  Este boilerplate usa FastAPI con los controladores de rutas `async`, Pydantic para la validación y la configuración, y el SDK de Python `dodopayments`. Los controladores llaman al cliente síncrono `DodoPayments`. Para evitar bloquear el bucle de eventos, cambia a `AsyncDodoPayments` y `await` sus llamadas.
</Info>

### Características

El boilerplate incluye:

* **Configuración rápida**: pasa de clonar el repositorio a tener un servidor en ejecución en unos cinco minutos.
* **Controladores asíncronos**: los controladores de rutas son funciones `async def` de FastAPI.
* **Sesiones de checkout**: un endpoint de checkout preconfigurado que usa el SDK de Python.
* **Gestión de webhooks**: un endpoint de webhook que verifica cada firma con el método `unwrap` del SDK.
* **Customer Portal**: un endpoint que crea sesiones del Customer Portal.
* **Seguridad de tipos**: los modelos de Pydantic validan los cuerpos de las solicitudes y el código usa sugerencias de tipos.
* **Configuración del entorno**: `pydantic-settings` carga y valida la configuración desde `.env`.

## Requisitos previos

Antes de comenzar, necesitas:

* **Python 3.9 o posterior**, que es lo que requiere el SDK `dodopayments`. Se recomienda Python 3.11 o posterior.
* **pip** o **uv** para la gestión de paquetes.
* **Una cuenta de Dodo Payments**, para crear una API key y un secreto de firma de webhook en el dashboard.

## Inicio rápido

<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">
    Configura un entorno de Python aislado:

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

    También puedes usar uv para gestionar las dependencias más rápidamente:

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

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

    O con uv:

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

  <Step title="Get API Credentials">
    Regístrate en [Dodo Payments](https://dodopayments.com/) y obtén tus credenciales desde el dashboard:

    * **API Key:** crea una clave en [Dashboard → Developer → API Keys](https://app.dodopayments.com/developer/api-keys).
    * **Webhook Key:** añade un endpoint en [Dashboard → Developer → Webhooks](https://app.dodopayments.com/developer/webhooks) y copia su secreto de firma. La URL del endpoint debe ser pública y usar HTTPS. Para recibir eventos en tu máquina, consulta [Probar webhooks localmente](#testing-webhooks-locally).

    <Tip>
      Crea ambas mientras el interruptor de **Live Mode** en la barra lateral esté desactivado. Una clave de modo de prueba solo funciona con `DODO_PAYMENTS_ENVIRONMENT=test_mode` y los pagos en modo de prueba no mueven dinero real.
    </Tip>
  </Step>

  <Step title="Configure Environment Variables">
    Copia el archivo de ejemplo para crear un archivo `.env` en el directorio raíz:

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

    Establece los valores con tus credenciales de Dodo Payments:

    ```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
    ```

    Las cuatro variables son obligatorias. `app/core/config.py` las carga con `pydantic-settings` y la aplicación no se inicia si falta alguna o está vacía. `DODO_PAYMENTS_RETURN_URL` es el lugar al que checkout envía al cliente después del pago.

    <Warning>
      No confirmes el archivo `.env` en el control de versiones. El `.gitignore` del repositorio ya lo excluye.
    </Warning>
  </Step>

  <Step title="Add Your Products">
    Sustituye los productos de ejemplo en `app/lib/products.py` por los tuyos. Establece cada `product_id` con el ID de un producto en **Products** en tu dashboard. La página de precios muestra estos productos.
  </Step>

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

    Abre [http://localhost:8000/docs](http://localhost:8000/docs) para ver la documentación interactiva de la API.

    <Check>
      Swagger UI muestra los endpoints `/api/checkout/`, `/api/webhook/` y `/api/customer-portal/`, listos para probar.
    </Check>

    La URL raíz, `http://localhost:8000`, sirve la página de precios.

    <Warning>
      `app/main.py` llama a `templates.TemplateResponse("index.html", {"request": request, ...})`, una firma que Starlette 1.x ya no acepta, por lo que la página de precios devuelve un error `500` en una instalación nueva. Para solucionarlo, cambia la llamada a `templates.TemplateResponse(request, "index.html", {"products": products})`.
    </Warning>
  </Step>
</Steps>

## Estructura del proyecto

```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
```

## Endpoints de la API

`app/main.py` monta cada router con un prefijo `/api`:

| Endpoint | Método | Descripción |
| - | - | - |
| `/api/checkout/` | POST | Crear una nueva sesión de checkout |
| `/api/webhook/` | POST | Gestionar webhooks de Dodo Payments |
| `/api/customer-portal/` | POST | Generar la URL del Customer Portal |

Cada ruta termina con una barra. FastAPI responde a una solicitud a la ruta sin la barra con una redirección `307`, así que usa la ruta exacta, especialmente en la URL de tu webhook.

## Ejemplos de código

Estos ejemplos están resumidos a partir de los archivos de `app/api/`.

### Crear una sesión de checkout

`app/api/checkout.py` crea una sesión de checkout y devuelve su `checkout_url`. El cuerpo de la solicitud acepta un `product_id`, un `quantity` opcional y un objeto `customer` opcional con `name` y `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))
```

### Gestionar webhooks

`app/api/webhook.py` verifica la firma con el método `unwrap` del SDK y, a continuación, distingue según el tipo de evento:

```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}
```

### Integración con Customer Portal

`app/api/portal.py` crea una sesión de Customer Portal para un ID de cliente y devuelve el enlace al portal como `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))
```

La página de precios en `app/templates/index.html` envía un ID de cliente codificado directamente (`cus_001`) a este endpoint, y un nombre y correo electrónico codificados directamente al endpoint de checkout. Sustitúyelos por los valores del usuario que ha iniciado sesión.

## Eventos de webhook

El controlador en `app/api/webhook.py` distingue entre estos eventos:

| Evento | Descripción |
| - | - |
| `payment.succeeded` | Pago completado correctamente |
| `payment.failed` | El intento de pago falló |
| `subscription.active` | La suscripción ahora está activa |
| `subscription.cancelled` | La suscripción se canceló |

Para gestionar otro evento, añade una rama para su tipo, como `refund.succeeded` para un reembolso procesado correctamente. Consulta todos los tipos de eventos en la [guía de eventos de webhook](/developer-resources/webhooks/intents/webhook-events-guide).

Añade tu lógica de negocio dentro del controlador del webhook para:

* Actualizar los permisos de usuario en tu base de datos
* Enviar correos electrónicos de confirmación
* Aprovisionar acceso a productos digitales
* Hacer seguimiento de análisis y métricas

## Probar webhooks localmente

Dodo Payments no puede acceder a `localhost`. Para el desarrollo local, usa una herramienta como [ngrok](https://ngrok.com/) para exponer tu servidor local:

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

Añade la URL HTTPS de ngrok, seguida de `/api/webhook/`, como endpoint en tu [panel de Dodo Payments](https://app.dodopayments.com/developer/webhooks):

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

Copia el secreto de firma del endpoint en `DODO_PAYMENTS_WEBHOOK_KEY` dentro de `.env` y reinicia el servidor. La aplicación lee `.env` únicamente al iniciarse.

## Implementación

### Docker

El repositorio no incluye un `Dockerfile`. Para ejecutar la aplicación en un contenedor, añade este `Dockerfile` a la raíz del repositorio:

```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 . .` copia todos los archivos del contexto de compilación, incluido `.env`. Para mantener tus claves fuera de la imagen, añade un archivo `.dockerignore` que incluya `.env`. Después, compila la imagen y ejecútala con tu archivo de entorno:

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

### Consideraciones para producción

<Warning>
  Antes de implementar en producción:

  * Cambia `DODO_PAYMENTS_ENVIRONMENT` a `live_mode`.
  * Usa una clave de API del modo live del panel.
  * Añade un endpoint de webhook para tu dominio de producción y establece `DODO_PAYMENTS_WEBHOOK_KEY` en su secreto de firma.
  * Establece `DODO_PAYMENTS_RETURN_URL` en tu URL de producción.
  * Habilita HTTPS para todos los endpoints.
</Warning>

## Solución de problemas

<AccordionGroup>
  <Accordion title="Import errors or missing modules">
    Asegúrate de que tu entorno virtual esté activado y de que las dependencias estén instaladas:

    ```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` sirve archivos estáticos desde `app/static`, pero el repositorio no incluye ese directorio. Créalo con `mkdir app/static` y vuelve a iniciar el servidor.
  </Accordion>

  <Accordion title="Checkout session creation fails">
    Comprueba estas causas habituales:

    * El ID del producto no existe en tu panel de Dodo Payments.
    * La clave de API o `DODO_PAYMENTS_ENVIRONMENT` en `.env` es incorrecta. Una clave del modo de prueba solo funciona con `test_mode`.

    El endpoint devuelve el error del SDK en una respuesta `400`. Comprueba los registros de FastAPI para ver mensajes de error detallados.
  </Accordion>

  <Accordion title="Webhooks not receiving events">
    Para realizar pruebas locales, usa [ngrok](https://ngrok.com) para exponer tu servidor:

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

    En tu [panel de Dodo](https://app.dodopayments.com/developer/webhooks), añade un endpoint con la URL de ngrok seguida de `/api/webhook/`, incluida la barra final. Copia el secreto de firma de ese endpoint en `DODO_PAYMENTS_WEBHOOK_KEY` dentro de tu archivo `.env`.
  </Accordion>

  <Accordion title="Webhook signature verification fails">
    * Asegúrate de que `DODO_PAYMENTS_WEBHOOK_KEY` en `.env` coincida con el secreto de firma del endpoint.
    * Verifica la firma con el cuerpo sin procesar de la solicitud, antes de analizarlo como JSON.
    * Pasa los tres encabezados `webhook-id`, `webhook-timestamp` y `webhook-signature` a `client.webhooks.unwrap()`. La firma de Standard Webhooks cubre `id.timestamp.body`, no solo el cuerpo.
  </Accordion>
</AccordionGroup>

## Más información

<CardGroup cols={2}>
  <Card title="Python SDK" icon="python" href="/developer-resources/sdks/python">
    Documentación completa del SDK de Python con compatibilidad con async
  </Card>

  <Card title="Webhooks Documentation" icon="webhook" href="/developer-resources/webhooks">
    Aprende sobre todos los eventos de webhook y las prácticas recomendadas
  </Card>

  <Card title="Checkout Sessions" icon="credit-card" href="/developer-resources/checkout-session">
    Análisis detallado de la configuración de la sesión de checkout
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/introduction">
    Documentación completa de la API de Dodo Payments
  </Card>
</CardGroup>

## Soporte

Para obtener ayuda con el boilerplate:

* Haz preguntas en la [comunidad de Discord](https://discord.gg/bYqAp4ayYh).
* Informa de problemas y sigue las actualizaciones en el [repositorio de GitHub](https://github.com/dodopayments/fastapi-boilerplate).
* Envía un correo electrónico al [equipo de soporte](mailto:support@dodopayments.com).


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