Conceptos

Errores e idempotencia

Mover dinero exige que los reintentos sean aburridos y predecibles. Esta página explica cómo se ven los errores, cuáles puedes reintentar y cómo la idempotency key evita que un timeout se convierta en un pago doble.

Formato de error

Cualquier respuesta 4xx o 5xx devuelve exactamente la misma estructura: un objeto error con code (identificador estable en mayúsculas) y message (texto legible). Enruta tu lógica por code, nunca por el texto del mensaje.

422 Unprocessable Entity
{
  "error": {
    "code": "UNPROCESSABLE_ENTITY",
    "message": "Insufficient balance for asset USDC on POLYGON"
  }
}

Códigos que verás

HTTP

400Bad Request

El cuerpo no cumple el esquema: falta un campo requerido o un enum no es válido. No reintentes sin corregirlo.

401 / 403Auth

API key ausente, inválida o sin permiso sobre el recurso o entorno solicitado.

404Not Found

El id no existe o pertenece a otro merchant. Verifica que estés usando el mismo entorno.

409Conflict

Estado incompatible: por ejemplo aprobar una transferencia que ya fue aprobada o rechazada.

422Unprocessable

La petición es válida pero no se puede ejecutar: saldo insuficiente, token deshabilitado o red no soportada para ese activo.

429Rate limit

Demasiadas peticiones. Aplica backoff exponencial con jitter antes de reintentar.

5xxServer

Fallo temporal de nuestro lado. Reintenta con la misma idempotency_key: es seguro.

Idempotency keys

POST /v1/transfers exige idempotency_key (mínimo 8 caracteres). Es una promesa: la misma clave nunca ejecutará dos envíos, sin importar cuántas veces la reintentes.

  • Genera la clave desde tu propio dominio: el id del retiro, de la factura o de la orden. Nunca uses un UUID nuevo por reintento.
  • Persiste la clave antes de llamar a la API. Si tu proceso muere a mitad del request, al recuperarte reintenta con la misma clave.
  • Un timeout no significa que el envío falló. Reintenta con la misma clave o consulta GET /v1/transfers antes de asumir cualquier cosa.
El antipatrón más caro

Reintentar una transferencia con una clave distinta porque "la primera parecía fallida" es la forma más común de enviar el doble de fondos. La clave estable es tu red de seguridad.

Estrategia de reintentos

Reintenta 429 y 5xx con backoff exponencial (por ejemplo 1s, 2s, 4s, 8s, con jitter) y un máximo de 5 intentos. No reintentes 400, 404 ni 422: requieren un cambio en tu petición o en el estado de la cuenta.

retry.ts
async function withRetry<T>(fn: () => Promise<T>, attempts = 5): Promise<T> {
  let lastError: unknown;
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
      const status = (error as { status?: number }).status ?? 0;
      const retryable = status === 429 || status >= 500 || status === 0;
      if (!retryable) throw error;
      const delay = 2 ** i * 1000 + Math.random() * 250;
      await new Promise((r) => setTimeout(r, delay));
    }
  }
  throw lastError;
}