Introducción al diseño orientado a SaaS de planes, facturación y limitación de funciones en FastAPI: patrones prácticos para manejar de forma segura planes gratuitos, planes de pago y estados de suscripción
Resumen
- El diseño de planes en SaaS no consiste simplemente en “separar el precio mensual”. Su verdadera esencia es garantizar que toda la aplicación pueda manejar de forma coherente qué contrato permite qué funciones, y hasta qué punto.
- En FastAPI, más allá de la autenticación, el manejo de tenants y RBAC/ABAC, el diseño se vuelve mucho menos frágil cuando organizas la información del plan, el estado de la suscripción y los límites de uso como dependencias y funciones de política.
- Lo importante no es decidir solo por
plan. En los sistemas reales, debes diseñar en torno a estados de suscripción (active,trialing,past_due,canceled, etc.), add-ons, valores límite, períodos de gracia y cómo deben manejarse los pagos fallidos. - Ocultar botones en la UI no es suficiente para limitar funciones. Las decisiones finales siempre deben aplicarse en el backend, y la operación se vuelve mucho más sencilla si esas decisiones también pueden registrarse en audit logs y event logs.
- Este artículo organiza gradualmente el diseño del modelo de planes en FastAPI, feature flags, límites de uso, manejo de estados de facturación, transiciones de estado basadas en webhooks y estrategias de prueba.
A quién le resultará útil leer esto
Desarrolladores individuales y personas en aprendizaje
Esto es para quienes han empezado a pensar: “Quiero separar un plan gratuito de uno de pago” o “La exportación CSV debería estar disponible solo en planes de pago”.
Al principio, puede que quieras escribir algo sencillo como if user.plan == "pro", pero en cuanto aumentan las funciones, las cosas se vuelven caóticas rápidamente. Este artículo proporciona la base para dar un paso hacia un diseño más organizado.
Ingenieros backend en equipos pequeños
Esto es para equipos que construyen SaaS con FastAPI, donde cada tenant tiene un plan y las restricciones de funciones y límites de uso han ido aumentando gradualmente.
Ayuda a organizar preguntas operativas como “¿En qué capa debemos tomar la decisión?”, “¿Qué debería detenerse durante un fallo de pago?” y “¿Qué debería pasar cuando termina una prueba?” en una forma más fácil de implementar.
Equipos de desarrollo SaaS y startups
Esto es para equipos que enfrentan problemas reales como múltiples planes, add-ons, facturación por uso, períodos de gracia, pagos fallidos y reactivación tras inactividad.
En lugar de centrarse en el propio sistema de facturación, se centra en cómo la aplicación puede reflejar de forma segura el estado de la suscripción, ayudándote a revisar patrones de diseño que sean menos propensos a romperse más adelante.
Nota sobre accesibilidad
- El artículo muestra primero la visión general y luego avanza en el orden de “terminología”, “modelo de datos”, “límites de funciones”, “estado de facturación”, “webhooks” y “pruebas”, por lo que es fácil de seguir incluso si te incorporas a mitad.
- Los términos técnicos se explican brevemente cuando aparecen por primera vez, y luego se usan las mismas expresiones de forma consistente para reducir la carga cognitiva.
- Los ejemplos de código se dividen por pequeñas responsabilidades para que ningún bloque quede sobrecargado.
- El nivel objetivo es aproximadamente equivalente a AA.
1. El diseño de planes SaaS no es una “tabla de precios”, sino “control de autorización”
Cuando empiezas a construir un SaaS, lo primero que suele hacerse visible es la página de precios.
Es natural querer mostrar un plan Free, un plan Pro y un plan Enterprise, y desde una perspectiva de marketing esa parte destaca mucho.
Sin embargo, la esencia en el lado del backend no es cómo se muestra el precio.
Lo que realmente importa es determinar con seguridad qué puede usar ese tenant en este momento, hasta qué punto y bajo qué estado actual de suscripción.
Por ejemplo, incluso dentro del mismo “plan Pro”, la situación real puede variar así:
- En prueba
- Pagado y activo
- En período de gracia tras un fallo de pago
- Programado para cancelación
- Configurado para detenerse al final de este mes
- Con capacidad extra comprada solo mediante add-ons
Dada esa realidad, una comprobación simple como plan == "pro" no es suficiente.
Por eso, el centro del diseño necesita información como:
- Tipo de plan
- Estado de suscripción
- Fecha de expiración
- Límites de uso
- Add-ons
- Excepciones temporales o contratos especiales
Este artículo examina cómo mapear todo este panorama en dependencias y políticas de FastAPI.
2. Términos que conviene organizar primero: plan, suscripción, estado de suscripción y función
Para evitar mezclar conceptos, organicemos primero la terminología.
Plan
Un plan es un concepto cercano al “nombre de producto” del conjunto de funciones ofrecidas.
Ejemplos:
freestarterproenterprise
Un plan es relativamente estático y es la categoría que también aparece en la página de precios.
Subscription
Una suscripción es el registro contractual real que representa en qué plan está actualmente un tenant.
No es el plan en sí, sino un registro que contiene el estado contractual activo.
Subscription state
El estado de suscripción indica la facturación, la validez y la condición de detención.
Ejemplos:
trialingactivepast_duecanceledpausedexpired
En el trabajo real, si una función puede usarse o no a veces está influido más fuertemente por el estado de suscripción que por el nombre del plan.
Feature
Una feature es una capacidad que una persona usuaria puede usar realmente dentro de la aplicación.
Ejemplos:
- Crear proyectos
- Exportación CSV
- Acceso API
- Configuración de webhooks
- Ver audit logs
- Límites de capacidad de almacenamiento
- Límites de número de miembros
Cómo representar estas “features” es el núcleo del diseño del lado de la aplicación.
3. La política de diseño que deberías decidir primero: evita hardcodear nombres de planes
Una implementación común al principio se parece a esto:
if tenant.plan == "pro":
# CSV export allowed
Al principio, esto es muy fácil de entender.
Pero al poco tiempo aparecen rápidamente los siguientes problemas.
- Puede que quieras habilitar CSV también para
starter - Enterprise puede necesitar audit logs además de CSV
- Algunos clientes pueden necesitar CSV como excepción especial
- Puede que quieras desbloquear solo algunas funciones durante la prueba
- Durante un fallo de pago, puede que quieras detener solo CSV
Entonces if tenant.plan in {...} empieza a extenderse por todas partes, y después resulta imposible seguirlo correctamente.
Así que desde el principio recomiendo la siguiente política:
- Trata los nombres de los planes como etiquetas de producto
- Realiza las comprobaciones reales de funciones usando “feature keys”
- Gestiona los límites de uso por separado como valores numéricos
- Trata el estado de suscripción de forma independiente del plan
Con esta estructura, incluso si reorganizas los planes más adelante, se necesitarán menos cambios en el lado de la aplicación.
4. Modelo de datos básico: separa Tenant, Subscription y PlanFeature
Primero creemos un modelo conceptual mínimo.
4.1 Tenant
Un tenant es la propia organización cliente.
# app/models/tenant.py
from pydantic import BaseModel
class Tenant(BaseModel):
id: int
name: str
is_active: bool = True
4.2 Subscription
El contrato real debería almacenarse por separado.
# app/models/subscription.py
from pydantic import BaseModel
from typing import Literal
from datetime import datetime
PlanCode = Literal["free", "starter", "pro", "enterprise"]
SubscriptionStatus = Literal[
"trialing",
"active",
"past_due",
"paused",
"canceled",
"expired",
]
class Subscription(BaseModel):
tenant_id: int
plan_code: PlanCode
status: SubscriptionStatus
current_period_end: datetime | None = None
cancel_at_period_end: bool = False
trial_ends_at: datetime | None = None
4.3 Plan features
La relación entre planes y funciones debería mantenerse en una tabla o configuración separada.
# app/models/plan_feature.py
from pydantic import BaseModel
class PlanFeature(BaseModel):
plan_code: str
feature_key: str
enabled: bool = True
limit_value: int | None = None
Si lo estructuras así, incluso cuando quieras añadir una nueva función al plan pro, solo necesitas actualizar la relación entre el plan y la función.
5. Define primero las feature keys: fija nombres orientados a máquina antes que nombres orientados a personas
Si vas a implementar restricciones de funciones, primero necesitas una forma legible por máquina de expresar qué es exactamente lo que se está restringiendo.
Por ejemplo, claves como estas:
project.createproject.export_csvaudit_log.viewapi.accesswebhook.createmember.max_countstorage.max_bytes
Lo importante aquí es separar las funciones de tipo booleano de las funciones basadas en límites.
Boolean features
- Se puede usar / no se puede usar
Ejemplos:
audit_log.viewproject.export_csv
Limit-based features
- Hasta cuántos, cuántas personas, cuántos GB
Ejemplos:
member.max_count = 5storage.max_bytes = 1073741824
Sin esta distinción, el diseño se vuelve muy difícil de gestionar más adelante.
6. Política básica en FastAPI: obtén la información de la suscripción mediante dependencias
En FastAPI, al igual que con usuarios autenticados e información del tenant, es más fácil mantener las cosas organizadas si obtienes la suscripción actual a través de una dependencia.
6.1 Obtener el tenant actual
Asumamos, en línea con artículos anteriores, que el ID del tenant actual ya está resuelto.
# app/deps/tenant.py
def get_current_tenant_id() -> int:
return 10
6.2 Obtener la suscripción actual
# app/deps/subscription.py
from fastapi import Depends, HTTPException, status
from app.deps.tenant import get_current_tenant_id
from app.models.subscription import Subscription
def get_current_subscription(
tenant_id: int = Depends(get_current_tenant_id),
) -> Subscription:
# In reality, fetch from DB or cache
if tenant_id == 10:
return Subscription(
tenant_id=10,
plan_code="pro",
status="active",
)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="subscription not found",
)
Con esto, cada router y cada service layer puede referirse de forma consistente a “la información de suscripción del tenant actual”.
7. Convierte las comprobaciones de funciones en funciones: has_feature() y get_limit()
Para evitar dispersar nombres de planes por toda la app, prepara funciones de comprobación de funciones.
# app/services/plan_service.py
from app.models.subscription import Subscription
FEATURE_MATRIX = {
"free": {
"project.export_csv": False,
"audit_log.view": False,
"api.access": False,
"member.max_count": 3,
"storage.max_bytes": 100 * 1024 * 1024,
},
"pro": {
"project.export_csv": True,
"audit_log.view": True,
"api.access": True,
"member.max_count": 20,
"storage.max_bytes": 5 * 1024 * 1024 * 1024,
},
"enterprise": {
"project.export_csv": True,
"audit_log.view": True,
"api.access": True,
"member.max_count": 9999,
"storage.max_bytes": 100 * 1024 * 1024 * 1024,
},
}
# app/services/plan_service.py (continued)
def has_feature(subscription: Subscription, feature_key: str) -> bool:
plan_features = FEATURE_MATRIX.get(subscription.plan_code, {})
value = plan_features.get(feature_key, False)
return bool(value) if isinstance(value, bool) else True
def get_limit(subscription: Subscription, feature_key: str) -> int | None:
plan_features = FEATURE_MATRIX.get(subscription.plan_code, {})
value = plan_features.get(feature_key)
return value if isinstance(value, int) else None
Esta implementación es solo un ejemplo mínimo, pero el punto importante es centralizar la lógica de decisión en un solo lugar.
8. Incluye siempre el estado de suscripción en la decisión: no asumas que solo active significa usable
Una omisión común en las comprobaciones de planes es el estado de suscripción.
Por ejemplo, incluso con un plan pro, si el estado es past_due, puede que necesites distinguir entre “funciones que pueden seguir usándose” y “funciones que deberían detenerse”.
Así que en lugar de mirar solo has_feature(), también deberías comprobar si la suscripción está en un estado utilizable.
# app/services/subscription_policy.py
from app.models.subscription import Subscription
ACTIVE_LIKE = {"trialing", "active"}
def is_subscription_usable(subscription: Subscription) -> bool:
return subscription.status in ACTIVE_LIKE
Sin embargo, en la práctica, past_due a menudo no se corta de inmediato.
Por ejemplo, puede haber un período de gracia de 3 días después de un fallo de pago durante el cual se permite acceso de solo lectura, mientras que las operaciones de escritura se detienen.
Por eso es útil dividir los juicios de estado por tipo de operación, así:
# app/services/subscription_policy.py
def can_use_read_features(subscription: Subscription) -> bool:
return subscription.status in {"trialing", "active", "past_due"}
def can_use_write_features(subscription: Subscription) -> bool:
return subscription.status in {"trialing", "active"}
def can_use_export_features(subscription: Subscription) -> bool:
return subscription.status in {"active"}
Esta estructura te permite cambiar el comportamiento con flexibilidad durante un fallo de pago.
9. Expresa “feature requerida” mediante dependencias
De una manera más propia de FastAPI, usar dependencias para funciones requeridas hace que la intención del router sea más fácil de leer.
9.1 Restricción para una función tipo feature-flag
# app/deps/plan_permissions.py
from fastapi import Depends, HTTPException, status
from app.deps.subscription import get_current_subscription
from app.services.plan_service import has_feature
from app.services.subscription_policy import can_use_write_features
def require_csv_export_feature(
subscription = Depends(get_current_subscription),
):
if not can_use_write_features(subscription):
raise HTTPException(
status_code=status.HTTP_402_PAYMENT_REQUIRED,
detail="subscription is not usable for export",
)
if not has_feature(subscription, "project.export_csv"):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="csv export is not available for this plan",
)
return subscription
9.2 Usarlo en el router
# app/api/v1/routers/projects.py
from fastapi import APIRouter, Depends
from app.deps.plan_permissions import require_csv_export_feature
router = APIRouter(prefix="/projects", tags=["projects"])
@router.get("/export")
def export_projects_csv(
subscription = Depends(require_csv_export_feature),
):
return {"status": "export started", "plan": subscription.plan_code}
Ahora, la intención de que “la exportación CSV tiene condiciones de plan” puede leerse directamente desde el router.
10. Restricciones basadas en límites: manejar de forma segura recuentos, capacidad y límites de asientos
No solo son importantes las funciones de tipo booleano, sino también los límites de cantidad y capacidad en SaaS.
10.1 Ejemplo: límite de número de miembros
Si quieres que “free permita hasta 3 personas” y “pro permita hasta 20 personas”, deberías comprobar el límite antes de añadir.
# app/services/member_policy.py
from app.models.subscription import Subscription
from app.services.plan_service import get_limit
def can_add_member(subscription: Subscription, current_member_count: int) -> bool:
limit = get_limit(subscription, "member.max_count")
if limit is None:
return True
return current_member_count < limit
10.2 Usarlo en la service layer
# app/services/member_service.py
from fastapi import HTTPException, status
from app.models.subscription import Subscription
from app.services.member_policy import can_add_member
def invite_member(subscription: Subscription, current_member_count: int):
if not can_add_member(subscription, current_member_count):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="member limit reached",
)
return {"status": "invited"}
10.3 Ejemplo: límites de capacidad de almacenamiento
Esto también encaja bien con el diseño de subida de archivos, pero aquí se aplica la misma lógica a los límites de almacenamiento.
- Agregar el uso actual total
- Sumar el tamaño esperado de la nueva subida
- Rechazar si supera
storage.max_bytes
Si colocas esa comprobación en la service layer de upload, puedes mantener la coherencia con el plan.
11. Diseño de add-ons: prepararse para la realidad que no puede expresarse solo con planes
En productos SaaS reales, son comunes “compras adicionales” como las siguientes:
- Paquete extra de miembros
- Almacenamiento adicional
- Retención extendida de audit logs
- Cuota adicional de acceso API
- Slots extra de webhook
En estos casos, los planes por sí solos no son suficientes.
Por eso resulta útil un diseño que incluya addons además de subscription.
# app/models/addon.py
from pydantic import BaseModel
class Addon(BaseModel):
tenant_id: int
addon_key: str
quantity: int = 1
Por ejemplo, podrías calcular el storage.max_bytes final así:
- Valor base del plan
- Valor añadido por add-ons
- Override de campañas o contratos especiales
Si centralizas esto en una función como effective_limit(), será mucho más fácil cambiarlo más adelante.
12. Diseño de trial: pensarlo desde el principio hace las cosas más fáciles después
Los trials también afectan a los ingresos, así que vale la pena organizarlos desde el principio.
Políticas comunes
- Durante el trial, desbloquear parte o todas las funciones equivalentes a
pro - Una vez que
trial_ends_atha pasado, pasar aexpiredo a un estado equivalente afree - Decidir claramente si la facturación comienza automáticamente tras el trial o si espera a un upgrade manual
Lo importante en la práctica es no crear una lógica separada para juzgar el trial y otra distinta para juzgar el plan normal.
Un patrón recomendado es tratar el trial como una suscripción normal con status="trialing" y absorber el comportamiento dentro de las funciones de decisión.
13. Transiciones de estado basadas en webhooks: cómo reflejar eventos de facturación dentro de la app
En SaaS, el estado de suscripción suele cambiar mediante webhooks procedentes de sistemas de pago o facturación.
El punto importante aquí es separar responsabilidades para que, “cuando llega un evento externo, la app actualice su estado interno de Subscription”.
13.1 Eventos típicos
- Suscripción creada
- Trial iniciado
- Renovación exitosa
- Pago fallido
- Pago confirmado
- Cancelación programada
- Cancelación inmediata
- Cambio de plan
13.2 Mantener el estado como “source of truth” dentro de la app
En lugar de consultar el servicio externo en tiempo real cada vez, es más estable mantener el estado actual de Subscription dentro de la aplicación.
Las razones incluyen:
- Las respuestas API se vuelven más rápidas
- Las decisiones siguen funcionando durante caídas temporales del sistema externo
- Es más fácil de probar
- Es más fácil conectar cambios de estado con audit logs
Así que el manejo de webhooks debería seguir un flujo como este:
- Recibir el evento
- Verificar su legitimidad
- Actualizar
Subscription - Emitir audit logs o domain events si es necesario
14. Diseño UX durante un fallo de pago: ¿detener todo o detener por etapas?
El fallo de pago afecta tanto a la experiencia del producto como a los ingresos.
Si esta parte es ambigua, la operación se vuelve muy confusa.
Control por etapas habitual
- Inmediatamente después de pasar a
past_due- Lectura permitida, escritura bloqueada
- Tras finalizar el período de gracia
- Login permitido, funciones principales bloqueadas
- Suspensión total
- Todo fuertemente restringido excepto la pantalla de gestión de suscripción
Para reflejar esto en FastAPI, es más fácil dividir la granularidad de decisión como se mostró antes:
can_use_read_featurescan_use_write_featurescan_use_export_features
Esa estructura es mucho más fácil de manejar.
15. Integración con audit logs: registra siempre los cambios de facturación y de plan
Esto también se conecta con el contexto del artículo anterior, pero los cambios relacionados con planes y facturación son objetivos extremadamente importantes para audit logs.
Ejemplos de eventos que querrás registrar:
subscription.createdsubscription.plan_changedsubscription.past_duesubscription.canceledsubscription.reactivatedaddon.addedaddon.removed
Estos eventos son útiles tanto para atención al cliente como para investigación de incidentes.
Para explicar “¿Por qué ayer estaba disponible la exportación CSV y hoy no?”, necesitas el historial del estado de suscripción.
16. Combinar con RBAC/ABAC: trata las restricciones del plan como parte de la autorización
La conexión importante con el artículo anterior sobre RBAC/ABAC es que las restricciones del plan también deberían tratarse como parte de la autorización.
Por ejemplo, que la exportación CSV esté permitida puede ser el resultado de las siguientes condiciones acumuladas:
- RBAC
viewerno está permitido
- ABAC
- Debe pertenecer al mismo tenant
- Subscription state
- Debe ser
active
- Debe ser
- Plan feature
project.export_csvdebe estar habilitada
Así que la autorización final se convierte en una composición como esta:
Authenticated
AND belongs to tenant
AND required role
AND required attribute conditions
AND subscription state OK
AND plan feature OK
Pensar en este orden hace que sea sorprendentemente fácil de organizar aunque parezca complejo.
17. Patrones comunes de fallo
17.1 tenant.plan == "pro" está disperso por todo el código
Más adelante ya no podrás manejar con seguridad cambios de plan o contratos excepcionales.
Muévelo siempre a funciones o políticas.
17.2 No comprobar el estado de suscripción
Incluso un contrato pro puede estar en past_due o canceled.
Necesitas tratar plan_code y status por separado.
17.3 Restringir solo en la UI sin proteger el backend
Ocultar un botón no significa nada si la API puede seguir siendo llamada.
La decisión final siempre debe aplicarse en el lado de FastAPI.
17.4 Hardcodear valores límite como constantes
Si “hasta 5” y “hasta 10” están dispersos por ahí, los cambios de plan crearán accidentes.
Centralízalos en funciones como get_limit().
17.5 Usar eventos de webhook directamente para decisiones de negocio
Si dependes solo del estado inmediato en el momento en que llega el evento externo, el diseño se vuelve inestable.
Es más seguro almacenar un estado normalizado de Subscription dentro de la aplicación.
18. Estrategia de pruebas: las limitaciones de planes se rompen más fácilmente de lo que parece
La combinación de planes y estados de facturación es más frágil de lo que parece.
Por eso es mejor protegerla con tests unitarios y tests de API.
18.1 Tests mínimos que deberías tener
freeno puede exportar CSVpropuede exportar CSV- Incluso
prono puede exportar mientras esté enpast_due freeno puede añadir miembros después de alcanzar el límite- Enterprise puede ver audit logs
- La cancelación programada sigue permitiendo el uso durante el período activo
- El comportamiento tras terminar el trial es exactamente el previsto
18.2 Ejemplo de test unitario
# tests/test_plan_service.py
from app.models.subscription import Subscription
from app.services.plan_service import has_feature, get_limit
def test_pro_has_csv_export():
subscription = Subscription(tenant_id=10, plan_code="pro", status="active")
assert has_feature(subscription, "project.export_csv") is True
def test_free_member_limit():
subscription = Subscription(tenant_id=10, plan_code="free", status="active")
assert get_limit(subscription, "member.max_count") == 3
18.3 Ejemplo de test de estado de suscripción
# tests/test_subscription_policy.py
from app.models.subscription import Subscription
from app.services.subscription_policy import can_use_export_features
def test_past_due_cannot_export():
subscription = Subscription(tenant_id=10, plan_code="pro", status="past_due")
assert can_use_export_features(subscription) is False
Si mantienes estos tests de funciones puras con una cobertura razonable, las revisiones de planes se vuelven mucho más seguras.
19. Hoja de ruta según el tipo de lector
Para desarrolladores individuales y personas en aprendizaje
- Empieza solo con
freeypro - Evita hardcodear
tenant.plany creahas_feature() - Maneja al menos un valor límite, como el número de miembros, mediante
get_limit() - Añade restricciones tipo 403 o 402 a las APIs principales
Para ingenieros en equipos pequeños
- Crea una “lista de funciones” antes que una tabla de precios
- Separa el modelo en
plan_code,statusylimit - Usa dependencias para obtener la información de suscripción actual
- Mueve las comprobaciones inline de los routers a funciones de política
- Registra eventos de cambio de suscripción en audit logs
Para equipos SaaS y startups
- Organiza las responsabilidades entre planes, add-ons y estados de facturación
- Sincroniza el estado de
Subscriptionmediante webhooks - Documenta explícitamente períodos de gracia y políticas de suspensión
- Integra las restricciones del plan en la misma capa de autorización que RBAC/ABAC
- Refleja también estas restricciones en OpenAPI, pantallas de administración y procedimientos de atención al cliente
Referencias
- FastAPI Documentation
- FastAPI Dependencies
- FastAPI Security
- Pydantic Documentation
- OWASP Authorization Cheat Sheet
Conclusión
- El diseño de planes orientado a SaaS no es solo una tabla de precios, sino el diseño de autorización, gestión de límites y gestión de estados en toda la aplicación.
- En FastAPI, el diseño se vuelve mucho más estable si obtienes la información de suscripción actual mediante dependencias y centralizas las comprobaciones de funciones y límites en funciones de política.
- Trata
plan_codeystatuspor separado, y si es necesario permite añadir add-ons y contratos excepcionales por encima. Eso hace que el diseño sea mucho más sólido ante cambios futuros. - Es importante no solo controlar la visualización en la UI, sino también aplicar las restricciones de funciones en el backend y protegerlas mediante audit logs y pruebas.
- No necesitas construir una base de facturación perfecta desde el primer día, pero si separas desde el inicio “feature keys”, “limits” y “subscription states”, el sistema se vuelve mucho más resistente a medida que crece.
Un siguiente artículo natural en este flujo sería algo como “patrones de diseño en FastAPI para APIs internas de paneles de administración” o “implementación segura de APIs receptoras de webhooks”.

