Conceptos
Reintentos y backoff
Qué reintentan los SDK por su cuenta (429, 502, 503, 504 con backoff exponencial, jitter y Retry-After), por qué las escrituras no se reintentan por defecto y cómo activarlo de forma segura.
En esta página
Los cinco SDK traen la misma política de reintentos, pensada para que reintentar nunca duplique una operación.
La política por defecto #
| Parámetro | Valor por defecto | Qué hace |
|---|---|---|
| Intentos máximos | 3 (el primero + 2 reintentos) | 1 desactiva los reintentos. |
| Base del backoff | 0,5 s | Espera del primer reintento, duplicándose en cada uno. |
| Tope del backoff | 30 s | Ninguna espera supera este valor. |
| Jitter | completo | Cada espera es aleatoria entre 0 y el valor calculado, para no sincronizar a todos los clientes. |
Retry-After | se respeta | Si el gateway lo envía, el SDK espera exactamente eso. |
| Estados que se reintentan | 429, 502, 503, 504 | Fallos transitorios. Nunca 4xx de negocio ni 500. |
| Métodos | solo GET | Las escrituras se reintentan solo con retry_writes activado. |
| Errores de red sin respuesta | no se reintentan | Los reenvías tú con la misma Idempotency-Key. |
flowchart TD
A[Petición] --> B{¿Respuesta?}
B -- no: timeout / red --> X[Excepción de transporte<br/>reenvía tú con la misma llave]
B -- sí --> C{¿429/502/503/504?}
C -- no --> D[Éxito o error tipado]
C -- sí --> E{¿GET o retry_writes?}
E -- no --> D
E -- sí --> F{¿Quedan intentos?}
F -- no --> D
F -- sí --> G[Espera Retry-After<br/>o backoff con jitter] --> A¿Por qué las escrituras no se reintentan solas? #
El gateway sí deduplica: cada escritura con Idempotency-Key repetida devuelve la respuesta
guardada (idempotent_replay: true) en lugar de ejecutarse otra vez, en todas las rutas con llave.
La razón es otra: el SDK no puede saber qué versión del gateway tiene enfrente, y reintentar una
escritura contra un despliegue antiguo sin esa garantía la ejecutaría dos veces. Por eso el valor
seguro por defecto es reintentar solo GET.
Cuando sepas que tu gateway deduplica (todos los entornos de Compense lo hacen desde la versión
actual del contrato), activa los reintentos de escrituras. El SDK reutiliza la misma
Idempotency-Key en todos los intentos de una misma llamada, así que el servidor convierte el
reintento en una repetición:
from compense_gateway import CompenseClient, RetryConfig
client = CompenseClient(
api_key=os.environ["COMPENSE_API_KEY"],
environment="sandbox",
retry=RetryConfig(max_attempts=5, backoff_base=0.5, backoff_max=30.0, jitter=True,
retry_writes=True),
)const client = new CompenseClient({
apiKey: process.env.COMPENSE_API_KEY!,
environment: "sandbox",
retry: { maxAttempts: 5, backoffBaseMs: 500, backoffMaxMs: 30_000, jitter: true, retryWrites: true },
});use Compense\Sdk\CompenseClient;
use Compense\Sdk\RetryConfig;
$client = new CompenseClient(
apiKey: getenv('COMPENSE_API_KEY'),
environment: 'sandbox',
retry: new RetryConfig(maxAttempts: 5, backoffBase: 0.5, backoffMax: 30.0, jitter: true, retryWrites: true),
);var client = new CompenseClient(new CompenseClientOptions
{
ApiKey = Environment.GetEnvironmentVariable("COMPENSE_API_KEY")!,
Environment = CompenseEnvironment.Sandbox,
Retry = new RetryOptions
{
MaxAttempts = 5,
BackoffBase = TimeSpan.FromSeconds(0.5),
BackoffMax = TimeSpan.FromSeconds(30),
Jitter = true,
RetryWrites = true,
},
});client, err := compense.NewClient(os.Getenv("COMPENSE_API_KEY"),
compense.WithEnvironment(compense.Sandbox),
compense.WithRetry(compense.RetryConfig{MaxAttempts: 5, RetryWrites: true}),
// equivalente para solo encender las escrituras: compense.WithRetryWrites(true)
)# Con HTTP directo, el reintento es tuyo. Reintenta solo 429/502/503/504 y errores de red,
# respetando Retry-After, y SIEMPRE con la misma Idempotency-Key.
curl --retry 4 --retry-max-time 60 -X POST https://sandbox.dev.compense.co/gw/v1/gateway/loans/CRD-00123/payments/ \
-H "X-API-Key: $COMPENSE_API_KEY" -H "Idempotency-Key: horizonte-rec-2026-001" \
-H "Content-Type: application/json" \
-d '{"amount":"467009.88","reference":"REC-2026-001","channel":"CASH"}'En .NET con AddCompense, la misma política se configura en appsettings.json
("Compense": { "Retry": { "MaxAttempts": 5, "RetryWrites": true } }); en Go, un campo en cero de
RetryConfig toma el valor por defecto.
Timeouts #
El timeout es por intento: Python timeout= (segundos), JS timeout (milisegundos), PHP
timeout: (segundos, cuando el SDK construye Guzzle), .NET Timeout (TimeSpan), Go
WithTimeout (o el context.Context de cada llamada, que también corta las esperas entre
reintentos). Un timeout es un error sin respuesta: no sabes si la escritura se aplicó, así que
reenvíala con la misma llave (Idempotencia).
Qué hacer con cada fallo #
| Fallo | El SDK | Tú |
|---|---|---|
429 | Reintenta (GET, o escrituras con retry_writes) respetando Retry-After. | Si se agotan los intentos, encola y baja el ritmo (Límites). |
502/504 | Reintenta igual que 429. | Si persiste, reintenta más tarde con la misma llave y cita el trace_id. |
503 | Reintenta. | Igual que 502. |
500 | No reintenta. | Reporta el trace_id a soporte. |
409 en curso | No reintenta. | Espera y repite con la misma llave. |
4xx de negocio | No reintenta. | Corrige la petición o el caso: reintentar no lo arregla. |
| Red / timeout | No reintenta. | Reenvía con la misma Idempotency-Key. |