Integración segura con APIs de terceros para mantener tu app en funcionamiento durante caídas. Aprende sobre timeouts, reintentos, circuit breakers y comprobaciones rápidas.

Una API de terceros puede fallar de maneras que no parecen un evento "caído" limpio. El problema más común es la lentitud: las solicitudes se quedan colgadas, las respuestas llegan tarde y tu app sigue esperando. Si esas llamadas están en el camino crítico, un pequeño tropiezo fuera de tu control se acumula dentro de tu sistema.
Así es como una desaceleración local se convierte en una caída completa. Hilos o workers quedan esperando, las colas crecen, las transacciones de base de datos permanecen abiertas más tiempo y las nuevas solicitudes empiezan a expirar. En poco tiempo, incluso páginas que no usan la API externa se sienten rotas porque el sistema está sobrecargado por trabajo en espera.
El impacto es concreto. Un proveedor de identidad inestable bloquea registros e inicios de sesión. Un timeout en la pasarela de pagos congela el checkout, dejando a los usuarios sin saber si se les cobró. Un retraso en mensajería detiene restablecimientos de contraseña y confirmaciones de pedido, lo que desencadena una segunda ola de reintentos y tickets de soporte.
El objetivo es simple: aislar las fallas externas para que los flujos principales sigan avanzando. Eso puede significar permitir que un usuario realice un pedido mientras confirmas el pago más tarde, o aceptar un registro aunque falle el envío del correo de bienvenida.
Una métrica práctica de éxito: cuando un proveedor está lento o caído, tu app debe seguir respondiendo rápido y de forma clara, y el radio de impacto debe mantenerse pequeño. Por ejemplo, la mayoría de las solicitudes principales aún terminan dentro de tu presupuesto de latencia normal, las fallas se mantienen confinadas a las funciones que realmente dependen de esa API, los usuarios ven un estado claro (en cola, pendiente, inténtalo más tarde) y la recuperación ocurre automáticamente cuando el proveedor vuelve.
La mayoría de las fallas son previsibles, aunque no lo sea su momento. Nómbralas desde el principio y podrás decidir qué reintentar, qué detener y qué mostrar al usuario.
Categorías comunes:
No todos los errores significan lo mismo. Los problemas transitorios suelen merecer reintentos porque la siguiente llamada puede tener éxito (interrupciones de red, timeouts, 502/503 y algunos 429s después de esperar). Los problemas permanentes normalmente no se solucionan solos (credenciales inválidas, endpoints equivocados, solicitudes mal formadas, denegaciones de permiso).
Tratar todos los errores igual convierte un incidente pequeño en tiempo de inactividad. Reintentar fallas permanentes desperdicia tiempo, consume límites de tasa más rápido y crea un backlog que ralentiza todo lo demás. No reintentar fallas transitorias obliga a los usuarios a repetir acciones y descarta trabajo que podría completarse momentos después.
Presta atención extra a flujos donde una pausa se siente como una ruptura: checkout, inicio de sesión, restablecimiento de contraseña y notificaciones (email/SMS/push). Un pico de dos segundos en una API de marketing es molesto. Un pico de dos segundos en la autorización de pago bloquea ingresos.
Una prueba útil es: "¿Esta llamada es necesaria para terminar la tarea principal del usuario ahora mismo?" Si la respuesta es sí, necesitas timeouts estrictos, reintentos cuidados y un camino de fallo claro. Si no, muévela a una cola y mantén la app responsiva.
Un timeout es el tiempo máximo que estás dispuesto a esperar antes de parar y seguir adelante. Sin un límite claro, un proveedor lento puede acumular solicitudes en espera y bloquear trabajo importante.
Ayuda separar dos tipos de espera:
Elegir números no es cuestión de perfección. Es cuestión de coincidir con la paciencia humana y tu flujo de trabajo.
Una forma práctica de elegir timeouts es trabajar hacia atrás desde la experiencia:
El intercambio es real. Demasiado largo y consumes hilos, workers y conexiones a BD. Demasiado corto y creas fallos falsos y desencadenas reintentos innecesarios.
Los reintentos ayudan cuando una falla probablemente es temporal: un breve problema de red, un fallo de DNS o un 500/502/503 puntual. En esos casos, un segundo intento puede tener éxito y los usuarios no lo notan.
El riesgo es una tormenta de reintentos. Cuando muchos clientes fallan a la vez y todos reintentan simultáneamente, pueden sobrecargar al proveedor (y a tus propios workers). Backoff y jitter evitan eso.
Un presupuesto de reintentos te mantiene honesto. Mantén los intentos bajos y limita el tiempo total para que los flujos principales no queden esperando a otro servicio.
No reintentes errores predecibles del cliente como 400/422 (validación), 401/403 (auth) o 404s. Esos casi siempre fallarán de nuevo y solo añaden carga.
Una protección más: reintenta escrituras (POST/PUT) solo cuando tengas idempotencia en su lugar; de lo contrario corres el riesgo de cargos dobles o registros duplicados.
Idempotencia significa que puedes ejecutar la misma solicitud dos veces y obtener el mismo resultado final. Eso importa porque los reintentos son normales: las redes se caen, los servidores se reinician y los clientes expiran. Sin idempotencia, un reintento "útil" crea duplicados y problemas reales de dinero.
Imagina un checkout: la API de pago es lenta, tu app expira y reintenta. Si la primera llamada en realidad tuvo éxito, el reintento puede crear un segundo cargo. El mismo riesgo aparece en acciones como crear un pedido, iniciar una suscripción, enviar un email/SMS, emitir un reembolso o crear un ticket de soporte.
La solución es adjuntar una clave de idempotencia (o ID de solicitud) a cada llamada de "hacer algo". Debe ser única por acción del usuario, no por intento. El proveedor (o tu propio servicio) usa esa clave para detectar duplicados y devolver el mismo resultado en vez de ejecutar la acción otra vez.
Trata la clave de idempotencia como parte del modelo de datos, no como un header que esperas que nadie olvide.
Genera una clave cuando el usuario inicia la acción (por ejemplo, al hacer clic en Pagar), y guárdala con tu registro local.
En cada intento:
Si tú eres el "proveedor" para llamadas internas, aplica el mismo comportamiento en el servidor.
Un circuito breaker es un interruptor de seguridad. Cuando un servicio externo empieza a fallar, dejas de llamarlo por un breve periodo en vez de acumular más solicitudes que probablemente expiren.
Los circuit breakers suelen tener tres estados:
Cuando el breaker está abierto, tu app debe hacer algo predecible. Si una API de validación de direcciones está caída durante el registro, acepta la dirección y márcala para revisión posterior. Si una verificación de riesgo de pago está caída, encola el pedido para revisión manual o desactiva temporalmente esa opción y explícalo.
Elige umbrales que coincidan con el impacto en el usuario:
Mantén los enfriamientos cortos (segundos a un minuto) y limita las sondas half-open. El objetivo es proteger primero los flujos principales y luego recuperarse rápidamente.
Cuando una API externa está lenta o caída, tu objetivo es mantener al usuario en movimiento. Eso significa tener un Plan B honesto sobre lo que ocurrió.
Un fallback es lo que hace tu app cuando la API no puede responder a tiempo. Las opciones incluyen usar datos en caché, cambiar a un modo degradado (ocultar widgets no esenciales, desactivar acciones opcionales), pedir entrada al usuario en lugar de llamar a la API (entrada manual de dirección) o mostrar un mensaje claro con el siguiente paso.
Sé honesto: no digas que algo se completó si no fue así.
Si el trabajo no necesita terminar dentro de la solicitud del usuario, empújalo a una cola y responde rápido. Candidatos comunes: enviar correos, sincronizar con un CRM, generar informes y publicar eventos de analytics.
Falla rápido para acciones críticas. Si una API no es necesaria para completar el checkout (o la creación de cuenta), no bloquees la solicitud. Acepta el pedido, encola la llamada externa y concilia más tarde. Si la API es necesaria (por ejemplo autorización de pago), falla rápido con un mensaje claro y no hagas esperar al usuario.
Lo que el usuario vea debe coincidir con lo que ocurre detrás: un estado claro (completado, pendiente, fallado), una promesa que puedas cumplir (recibo ahora, confirmación después), una forma de reintentar y un registro visible en la UI (registro de actividad, indicador de pendiente).
Los límites de tasa son la forma que tiene un proveedor de decir: "Puedes llamarnos, pero no demasiado seguido." Los alcanzarás antes de lo que piensas: picos de tráfico, jobs en segundo plano que se disparan juntos o un bug que hace un loop sobre errores.
Empieza controlando cuántas solicitudes creas. Agrupa cuando sea posible, cachea respuestas incluso 30–60 segundos cuando sea seguro y limita en el cliente para que tu app no explote más rápido de lo que el proveedor permite.
Cuando recibas un 429 Too Many Requests, trátalo como una señal para reducir la velocidad.
Retry-After cuando esté presente.También limita la concurrencia. Un solo flujo (como sincronizar contactos) no debería consumir todos los workers y dejar sin recursos flujos críticos como login o checkout. Pools separados o límites por función ayudan.
Cada llamada a un tercero necesita un plan para fallar. No necesitas perfección. Necesitas un comportamiento predecible cuando el proveedor tenga un mal día.
Decide qué ocurre si la llamada falla ahora mismo. Un cálculo de impuestos durante el checkout puede ser imprescindible. Sincronizar un contacto de marketing normalmente puede esperar. Esta elección guía el resto.
Elige timeouts por tipo de llamada y mantenlos consistentes. Luego define un presupuesto de reintentos para no seguir golpeando una API lenta.
Si una solicitud puede crear algo o cobrar dinero, añade claves de idempotencia y guarda un registro de la solicitud. Si una petición de pago expira, un reintento no debería cobrar dos veces. El seguimiento también ayuda al soporte a responder: "¿Llegó?"
Cuando los errores aumentan, deja de llamar al proveedor por un corto periodo. Para llamadas imprescindibles, muestra un camino claro de "Intentar de nuevo". Para llamadas que pueden esperar, encola el trabajo y procésalo después.
Controla latencia, tasa de errores y eventos de apertura/cierre del breaker. Alerta en cambios sostenidos, no en picos aislados.
La mayoría de las caídas de APIs no comienzan grandes. Se hacen grandes porque tu app reacciona de la peor manera: espera demasiado, reintenta con demasiada agresividad y ocupa los mismos workers que mantienen todo lo demás en funcionamiento.
Estos patrones causan cascadas:
Pequeñas correcciones evitan grandes caídas: reintenta solo errores que probablemente sean temporales (timeouts, algunos 429s, algunos 5xx) y limita intentos con backoff y jitter; mantén timeouts cortos e intencionales; exige idempotencia para cualquier operación que cree o cobre; y diseña para fallos parciales.
Antes de poner una integración en producción, haz una pasada rápida con mentalidad de fallo. Si no puedes responder "sí" a un punto, trátalo como blocker de release para flujos críticos como registro, checkout o envío de mensajes.
Si un proveedor de pagos empieza a expirar, el comportamiento correcto es "el checkout aún carga, el usuario recibe un mensaje claro y no te quedas girando para siempre", no "todo se queda colgado hasta que expira."
Imagina un checkout que llama a tres servicios: una API de pagos para cobrar la tarjeta, una API de impuestos para calcular impuestos y una API de correo para enviar el recibo.
La llamada de pago es la única que debe ser síncrona. Los problemas en impuestos o correo no deberían bloquear la compra.
Supongamos que la API de impuestos a veces tarda 8 a 15 segundos. Si el checkout espera, los usuarios abandonan el carrito y tu app consume workers.
Un flujo más seguro:
Resultado: menos carritos abandonados y menos pedidos atascados cuando el proveedor de impuestos está lento.
El correo de recibo importa, pero nunca debe bloquear la captura de pago. Si la API de correo está fallando, el circuit breaker debería abrirse tras unos pocos fallos rápidos y detener las llamadas por una ventana de enfriamiento corta.
En lugar de enviar el correo en línea, encola un job de "enviar recibo" con una clave de idempotencia (por ejemplo order_id + email_type). Si el proveedor está caído, la cola reintentará en segundo plano y el cliente verá la compra como exitosa.
Resultado: menos tickets de soporte por confirmaciones faltantes y cero pérdida de ingresos por fallos en servicios no relacionados con el pago.
Elige un flujo que más duela cuando falla (checkout, registro, facturación) y hazlo tu integración de referencia. Luego copia los mismos valores por todas partes.
Un orden simple para desplegar:
Escribe tus valores predeterminados y mantenlos aburridos: un connect timeout, un request timeout, conteo máximo de reintentos, rango de backoff, tiempo de enfriamiento del breaker y las reglas de qué es reintentable.
Ejecuta un simulacro de fallo antes de expandir al siguiente flujo. Fuerza timeouts (o bloquea al proveedor en un entorno de prueba), luego confirma que el usuario ve un mensaje útil, los fallbacks funcionan y los reintentos en cola no se acumulan para siempre.
Si estás construyendo productos nuevos rápidamente, vale la pena convertir estos valores por defecto de fiabilidad en una plantilla reutilizable. Para equipos que usan Koder.ai (koder.ai), eso a menudo significa definir el timeout, reintentos, idempotencia y reglas de breaker una vez, y luego aplicar el mismo patrón en nuevos servicios mientras los generas e iteras.