Buenas prácticas
Lista de recomendaciones probadas para que tu integración sea robusta, segura y eficiente.
Seguridad
Almacena las credenciales en variables de entorno
# .env (nunca subir a Git)
TPVREADY_API_KEY=a3f8d9e2b1c4...
import os
API_KEY = os.environ["TPVREADY_API_KEY"]
Usa un secret manager en producción
En cloud (AWS, GCP, Azure) o en entornos serios, usa el secret manager nativo (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, Doppler…).
Nunca expongas la API Key al cliente final
Si tu aplicación tiene varios clientes con claves distintas, el cliente NO debe poder ver su propia clave. Solo debe verse un indicador "Conectado ✓".
Implementa rotación periódica
Avisa al cliente para que rote la clave cada 6-12 meses. Es fácil: regenerar desde el panel + actualizar la nueva clave en tu config.
Robustez
Implementa timeouts
Nunca dejes una petición HTTP sin timeout. Si la API tarda demasiado, falla rápido:
requests.get(url, headers=headers, timeout=20) # 20s razonable
Reintentos con backoff exponencial
Para errores 5xx o timeouts, reintenta con espera creciente:
import time
def with_retries(fn, max_attempts=4):
for attempt in range(max_attempts):
try:
return fn()
except (ConnectionError, Timeout):
if attempt == max_attempts - 1:
raise
time.sleep(2 ** attempt) # 1s, 2s, 4s, 8s
No reintentes errores 4xx (401, 403, 404, 422) — son problemas de tu petición.
Maneja la pérdida de validez de la credencial
Tu código debe distinguir entre:
- Error puntual (5xx) → reintentar
- Credencial inválida (401) → avisar al cliente, no reintentar
- Sin permiso (403) → avisar al cliente con instrucciones, no reintentar
Ver Códigos de error → Patrón robusto.
Idempotencia
Si una petición POST falla con timeout, no sabes si llegó al servidor. Para evitar duplicados:
- Antes de retry, busca primero si el recurso ya existe (
GET /clientes?q=...). - Si tu integración inserta lotes, usa identificadores externos únicos (CIF/NIF para clientes, referencia para productos) y trata duplicados como "ya creado".
Eficiencia
Cachea datos estáticos
El catálogo de productos, las tarifas y los maestros (formas de pago, países, IVA) cambian poco. Cáchealos localmente y refresca cada N minutos:
@lru_cache(maxsize=1)
def get_catalogo():
return llamar_api("GET", "/productos")
# Invalidar cada 10 min
def get_catalogo_fresco():
if cache_expirado():
get_catalogo.cache_clear()
return get_catalogo()
Paginación inteligente
Para listados grandes, no pidas page_size demasiado alto: 100-200 es buen equilibrio entre velocidad y carga del servidor.
Solicita solo lo que necesitas
Si solo necesitas listar nombres de clientes, usa GET /clientes/select (formato ligero) en lugar de GET /clientes (objeto completo).
Evita polling agresivo
Si necesitas estar al día de cambios:
- ✅ Polling razonable: cada 1-5 minutos
- ✅ Solo lo que pudo cambiar: filtra
?modificado_desde=<ultima_sync> - ❌ Nunca: cada 1-10 segundos a un endpoint pesado
Los webhooks (notificaciones push cuando algo cambia en TPVReady) están planificados. Cuando estén disponibles, son la forma correcta de reaccionar a eventos en tiempo real sin polling.
Observabilidad
Loguea peticiones y respuestas (sin la API Key)
logger.info(
"TPVReady call",
extra={
"method": method,
"path": path,
"status": resp.status_code,
"duration_ms": (time.time() - start) * 1000,
# ❌ NO loguees la API Key
},
)
Mide latencia y tasa de error
Mantén métricas:
- p50/p95/p99 de latencia por endpoint
- Tasa de errores 5xx
- Tasa de 401/403
Si la tasa de 401 sube de golpe → probablemente una clave de un cliente se revocó.
Alerta en errores persistentes
Si una integración con un cliente concreto da 401 repetidamente durante > 1 hora, notifica a tu soporte/al cliente. No dejes el error en silencio.
UX hacia tu cliente final
Mensajes claros, no técnicos
❌ "Error 403: scope 'productos:read' missing"
✅ "Tu integración no puede acceder al catálogo de productos. Pídele a tu administrador que añada el permiso 'Productos → READ' en TPVReady → Configuración → API Keys."
Indica qué scopes pides y por qué
Cuando un cliente conecte tu integración, explícale claramente:
Para que la integración funcione, necesita:
- Clientes → READ: para identificar quién escribe por WhatsApp.
- Productos → READ: para mostrar precios actualizados en las conversaciones.
No pedimos permisos de escritura ni acceso a facturas.
Tester de conexión
Incluye un botón "Probar conexión" en tu UI que:
- Llame a
GET /auth/me(si usas JWT) o a un endpoint trivial con la API Key. - Verifique que devuelve 200.
- Muestre OK o el error claro al cliente.
Anti-patrones que evitar
| ❌ Mal | ✅ Bien |
|---|---|
| Hardcodear la API Key en el código | Cargarla de env/secret manager |
| Reintentar 401/403 con backoff | Reintentar solo 5xx |
| Pedir paginación de 1000 items | Páginas de 100-200 |
| Loguear el body de errores incluyendo API Keys | Ofuscar/excluir credenciales del log |
| Polling cada segundo | Polling con frecuencia adecuada o webhooks |
| Mostrar "Error 500" al usuario final | "Tuvimos un problema, lo estamos solucionando" |
| Una sola API Key para varios clientes | Una clave por empresa cliente |