Idempotencia y límites de tasa
Haz que las mutaciones de la API de Tokeflow sean seguras de reintentar con claves de idempotencia, y diseña clientes resilientes que respeten los límites de tasa por clave.
Las redes fallan. Las solicitudes expiran. Los clientes reintentan. En cualquier superficie de pago, un reintento que crea silenciosamente un segundo cargo es un incidente de producción.
Tokeflow te ofrece dos primitivas para construir integraciones resilientes:
- Idempotencia — reintenta de forma segura las mutaciones de creación y cargo sin duplicar el trabajo.
- Límites de tasa — rendimiento predecible por clave, con señales claras para que tu cliente pueda retroceder de forma elegante.
Esta página cubre ambos, además de un helper de reintento listo para copiar y pegar.
Tokeflow es la capa de orquestación. Estandariza la superficie de la API, el enrutamiento y la semántica de reproducción descritos aquí; los proveedores de pago conectados realizan el procesamiento y la liquidación reales.
Idempotencia
Por qué importa la idempotencia
Considera un checkout que llama a POST /api/v1/transactions. La solicitud llega a Tokeflow, el cargo se inicia aguas abajo, pero la conexión se cae antes de que la respuesta regrese a tu servidor. Tu cliente no sabe si el cargo tuvo éxito. Si reintenta a ciegas, corres el riesgo de cobrarle dos veces al cliente.
Una clave de idempotencia elimina esa ambigüedad. Adjuntas una clave única a la mutación. Tokeflow registra el resultado contra esa clave. Si la misma clave llega de nuevo — ya sea desde un reintento automático, un doble clic o una reentrega de cola — Tokeflow devuelve el resultado original en lugar de realizar la operación por segunda vez.
La clave de idempotencia
Proporciona la clave de una de dos formas. El encabezado tiene precedencia si ambos están presentes.
| Ubicación | Field | Notas |
|---|---|---|
| Encabezado HTTP | Idempotency-Key: <unique> | Recomendado. No distingue mayúsculas de minúsculas. |
| Cuerpo de la solicitud | "idempotency_key": "<unique>" | Útil cuando el encabezado es difícil de establecer (p. ej. algunos relés de cola/webhook). |
El valor es una cadena opaca que tú generas. Debe ser única por operación lógica — es decir, la misma clave debe corresponder a exactamente una acción pretendida.
:::tip Construcción de clave recomendada Deriva la clave de forma determinista a partir de la operación de negocio, no aleatoriamente. Un buen patrón combina un identificador de negocio estable con un contador de intentos:
order_12345_attempt_1cart-a1b2c3:checkout- Un UUID v4 generado una vez por operación lógica y reutilizado a lo largo de los reintentos de esa operación.
La regla: cada reintento de la misma acción pretendida reutiliza la misma clave. Una acción genuinamente nueva (p. ej. el cliente elige pagar de nuevo tras un fallo) obtiene una nueva clave. :::
No generes una clave aleatoria nueva en cada intento HTTP. Eso anula el mecanismo — cada intento se trataría como un cargo nuevo y distinto.
Semántica de reproducción
La idempotencia tiene alcance por Merchant y por clave. La primera solicitud con una clave dada se ejecuta normalmente; las solicitudes posteriores con la misma clave reproducen el resultado almacenado.
| Escenario | Estado HTTP | Cuerpo |
|---|---|---|
Primera solicitud con la clave K | 201 Created | El recurso creado |
Reproducción con la misma clave K | 200 OK | El mismo cuerpo que la primera respuesta |
El código de estado es la señal: 201 significa que creaste algo nuevo, 200 significa que alcanzaste una reproducción. El cuerpo de la respuesta es idéntico en ambos casos, por lo que tu código puede tratarlos de manera uniforme y usar el estado solo para observabilidad.
La reproducción devuelve la respuesta original almacenada — no vuelve a ejecutar el enrutamiento ni vuelve a contactar al proveedor. Un 200 reproducido es una garantía de que no ocurrió ningún segundo cargo.
Qué endpoints aceptan claves de idempotencia
Usa claves de idempotencia en las mutaciones de creación y cargo — cualquier POST que tenga un efecto secundario que no querrías duplicar. La más importante es crear una transacción:
curl -X POST https://api.tokeflow.com/api/v1/transactions \
-H "Authorization: Bearer sk_live_mer_xxx" \
-H "Idempotency-Key: order_12345_attempt_1" \
-H "Content-Type: application/json" \
-d '{
"amount": 15000,
"currency": "BRL",
"payment_method": "credit_card",
"customer_id": "cust_7h2k9",
"payment_instrument_id": "pi_4a1c8",
"capture": true,
"metadata": { "campaign": "black_friday" }
}'Primera respuesta (201 Created):
{
"success": true,
"data": {
"id": "tx_9a8b7c6d",
"status": "authorized",
"amount": 15000,
"currency": "BRL",
"payment_method": "credit_card",
"charge_type": "payment",
"customer_id": "cust_7h2k9",
"metadata": { "campaign": "black_friday" },
"created_at": "2026-01-15T12:30:00.000Z"
},
"request_id": "req_8f3c2a91",
"timestamp": "2026-01-15T12:30:00.000Z"
}Reproducir la misma Idempotency-Key devuelve los data idénticos con HTTP 200 OK.
Idempotency-Key es obligatoria en POST /api/v1/transactions. Omitirla devuelve 400 validation_error. Esto es intencional — la creación de pagos siempre debe ser segura de reproducir.
{
"error": {
"type": "validation_error",
"code": "IDEMPOTENCY_KEY_REQUIRED",
"message": "Idempotency key is required. Provide it via Idempotency-Key header or idempotency_key in request body.",
"details": {},
"request_id": "req_1b2c3d4e",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}Orientación sobre TTL
Las claves de idempotencia se retienen durante una ventana limitada, no para siempre. Trata la garantía como algo que cubre el tiempo de vida activo de una operación y sus reintentos — típicamente de minutos a horas, muy dentro de cualquier presupuesto razonable de reintentos.
Implicaciones prácticas:
- Reintenta una operación con prontitud. No reutilices una clave días después y esperes una reproducción.
- Para un intento genuinamente nuevo tras una brecha larga, acuña una nueva clave.
- Nunca dependas de las claves de idempotencia como un almacén de deduplicación a largo plazo. Usa tus propios registros de pedido/transacción para eso.
Consulta Errores para la referencia completa de tipos de error y Transacciones para el contrato de creación de transacciones.
Límites de tasa
Cómo se cuentan los límites
Cada límite es un recuento de solicitudes en una ventana de 60 segundos, llevado por credencial y por endpoint: cada clave de API (o clave pública, en el caso del Bridge SDK) tiene su propio presupuesto en cada ruta, de modo que una ráfaga en un endpoint nunca agota el de otro, y una clave de Organization y una clave de Merchant nunca comparten presupuesto. El tope depende del tipo de tráfico que lleva la ruta.
| Clase de ruta | Rutas | Límite predeterminado por clave, por ruta |
|---|---|---|
| Pago | Escrituras que mueven dinero o cambian estado: creación de transacciones, capturas, anulaciones y reembolsos; sesiones de checkout y sus ítems; clientes, instrumentos de pago y acciones de suscripción. | 600 solicitudes / minuto |
| Lectura | Listados y consultas: transacciones, reembolsos, órdenes, sesiones de checkout, clientes, instrumentos de pago, suscripciones y sus transiciones. | 600 solicitudes / minuto |
| Eventos de checkout | POST /checkout-sessions/:id/events. También se limita por sesión de checkout, para que una sola sesión ruidosa no pueda agotar la clave. | 1200 solicitudes / minuto por clave; 60 / minuto por sesión |
| Bridge SDK | Rutas que el navegador llama con una clave pública — sesiones, tokens, tokens de pagador, contexto de checkout. También se limita por IP del cliente, para que un solo visitante no pueda agotar el presupuesto del merchant. | 600 solicitudes / minuto por clave pública |
| Verificación de tarjeta (SDK) | La comprobación de tarjeta con importe cero del SDK. Se mantiene baja y separada, porque es lo que atacaría un probador de tarjetas. | 60 solicitudes / minuto por clave pública |
| Todo lo demás | Rutas sin clase. | 100 solicitudes / minuto |
POST /checkout-sessions/:id/identify es una ruta de clase pago que también se limita por sesión de checkout.
Estos son valores predeterminados. Si una carga de trabajo necesita más — importaciones masivas, checkouts de alto volumen, trabajos de conciliación — contacta al equipo de Tokeflow: los límites se pueden ampliar para una clave específica, por clase de ruta. Una clave revocada o expirada pierde su presupuesto de inmediato.
Cuando alcanzas un límite
Una solicitud limitada se rechaza con 429 Too Many Requests antes de que llegue al recurso. La respuesta indica qué presupuesto la bloqueó y cuándo volver:
| Encabezado | Significado |
|---|---|
X-RateLimit-Limit | El límite del presupuesto que bloqueó la solicitud. |
X-RateLimit-Remaining | 0 en una solicitud rechazada. |
X-RateLimit-Reset | Segundos hasta que el contador de ese presupuesto se vacíe. |
Retry-After | Segundos hasta que puedas reintentar. |
{
"error": {
"type": "rate_limit_error",
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please retry after a short delay.",
"details": { "retry_after_seconds": 12, "limit": 600 },
"request_id": "req_9z8y7x6w",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}Diseñar teniendo en cuenta los límites de tasa
Un cliente robusto trata el 429 como una señal esperada y recuperable en lugar de un fallo.
- Respeta
Retry-After. Cuando un429lo incluye, espera al menos esa cantidad de segundos antes del siguiente intento. - Retrocede exponencialmente. Espera progresivamente más tiempo entre reintentos (p. ej. 0.5s, 1s, 2s, 4s) en lugar de martillar de inmediato.
- Añade jitter. Aleatoriza cada retraso para que los clientes concurrentes no reintenten al unísono y vuelvan a colisionar ("estampida").
- Limita los intentos. Ríndete tras un número acotado de reintentos y expón el error.
- Combínalo con la idempotencia. Cuando reintentes un
POSTtras un429, reutiliza la mismaIdempotency-Keypara que el éxito eventual no pueda cobrar por partida doble. - Distribuye la carga. Agrupa donde la API lo soporte, y evita bucles de sondeo apretados — prefiere webhooks para los cambios de estado.
Reintento con retroceso exponencial (Node)
Este helper reintenta ante 429 (y 5xx transitorios) usando retroceso exponencial con jitter completo. Como reenvía los mismos encabezados — incluida Idempotency-Key — en cada intento, las mutaciones reintentadas permanecen seguras.
/**
* fetch wrapper with exponential backoff + jitter for 429 / 5xx.
* Pass a stable Idempotency-Key in `init.headers` so retried POSTs are replay-safe.
*/
async function tokeflowFetch(url, init = {}, { maxRetries = 5, baseMs = 500 } = {}) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, init);
const retryable = res.status === 429 || (res.status >= 500 && res.status <= 599);
if (!retryable || attempt >= maxRetries) {
return res; // success, non-retryable error, or out of attempts
}
// Exponential backoff with full jitter: random delay in [0, base * 2^attempt].
const delay = Math.floor(Math.random() * baseMs * 2 ** attempt);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
// Usage — idempotency key generated once and reused across all retries.
const res = await tokeflowFetch("https://api.tokeflow.com/api/v1/transactions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEFLOW_SECRET_KEY}`,
"Idempotency-Key": "order_12345_attempt_1",
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 15000,
currency: "BRL",
payment_method: "credit_card",
customer_id: "cust_7h2k9",
payment_instrument_id: "pi_4a1c8",
capture: true,
}),
});
const { data } = await res.json();Mantén la clave secreta (sk_*) solo del lado del servidor. La lógica de reintento anterior pertenece a tu backend. Nunca incrustes una clave secreta en código de cliente de navegador o móvil.
Poniéndolo todo junto
| Objetivo | Uso |
|---|---|
| Prevenir cargos duplicados al reintentar | Idempotency-Key en POST /api/v1/transactions |
| Distinguir resultado nuevo vs reproducido | 201 (nuevo) vs 200 (reproducción) |
Sobrevivir a ráfagas y 429s | Retroceso exponencial + jitter |
| Evitar el doble cobro durante el retroceso | Misma clave de idempotencia en todos los reintentos |
Lecturas relacionadas:
- Autenticación — tipos de clave, alcances y el encabezado
Authorization. - Errores — cada tipo de error y su mapeo de estado HTTP.
- Transacciones — el contrato completo de creación de transacciones.
- Webhooks — reacciona a los cambios de estado en lugar de sondear.