Skip to content
LIQFYdocs
PTENENEN
Go to dashboard

Errores

Todo error de Liqfy es un objeto JSON con una estructura estable: programa tu manejador contra ella una sola vez y deja de adivinar. Toda respuesta, de éxito o de error, también incluye el encabezado X-Request-Id (generado cuando no envías uno, devuelto tal cual cuando lo envías).

#Estructura

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_request",
    "message": "Validation failed",
    "details": [
      { "param": "amount", "message": "must be a positive integer" }
    ]
  },
  "request_id": "req_01J..."
}
CampoSiempre presenteDescripción
error.typesíCategoría estable: una de invalid_request_error, authentication_error, permission_error, not_found_error, conflict_error, rate_limit_error, api_error.
error.codesíCódigo de máquina estable en snake_case. Es sobre esto que tu manejador debe ramificar, no sobre message.
error.messagesíExplicación legible por humanos. Seguro de registrar en logs; no es un contrato para automatización.
error.paramcuando falla exactamente un campoIdentifica ese único campo con error. Si fallan varios campos, está ausente y solo error.details los enumera.
error.detailsen fallos de validación{ param, message }[] estructurado para errores de validación de varios campos.
request_idsíCítalo al contactar con soporte: rastrea cada log, span y mensaje de Kafka. También se devuelve en el encabezado de respuesta X-Request-Id.

Alias de compatibilidad: el cuerpo de la respuesta también incluye algunos campos planos de nivel superior — statusCode, message, requestId y (cuando están presentes) code / fieldErrors — junto al objeto error, para clientes (incluido el panel) que los leen directamente. Lee el objeto anidado error; no se garantiza que los alias planos sobrevivan a una futura versión mayor.

#Mapa de códigos de estado

#Creación de cobro con resultado desconocido

409 idempotency_outcome_unknown significa que la creación original pudo haber llegado al proveedor de pagos, pero su resultado aún no está confirmado. Reintenta con la misma Idempotency-Key y el mismo cuerpo de solicitud, usando backoff. No generes una nueva clave para ese cobro: podría crear otro pago.

La operación original se conserva incluso después de cinco minutos o 24 horas. Un reintento recupera su resultado guardado cuando está disponible; de lo contrario, devuelve este error sin emitir otra creación. La ausencia de un resultado en la consulta no prueba que el cobro nunca se haya creado. Contacta con soporte indicando el request_id si el resultado sigue siendo desconocido. Las respuestas completadas permanecen disponibles durante al menos 24 horas.

#Estados HTTP

HTTPerror.typeAcción recomendada para el cliente
200——
201—Guarda el id devuelto y continúa.
400invalid_request_errorCorrige el cuerpo/los encabezados y reintenta. No reintentes a ciegas.
401authentication_errorVerifica que el encabezado Authorization: Bearer lleve una clave correcta y activa.
403permission_errorBloqueado por el alcance (scope), la protección antifraude/de velocidad, un intento de reembolso con clave de API (marcador de mensaje REFUND_ADMIN_ONLY) o la lista de IP permitidas. Revisa la solicitud o contacta con soporte.
404not_found_errorComprueba el id que proporcionaste.
409conflict_errorMisma Idempotency-Key usada con un cuerpo distinto (idempotency_key_reused), un primer intento aún en curso (idempotency_key_in_flight) o un estado no cancelable (payment_not_cancellable).
422invalid_request_errorViolación de regla de negocio (sigue teniendo forma de solicitud): lee message, corrige la entrada, no reintentes.
429rate_limit_errorReduce el ritmo. Reintenta con retardo exponencial + jitter; respeta Retry-After cuando esté presente.
5xxapi_errorEs seguro reintentar con la misma Idempotency-Key: la idempotencia te protege de duplicados.

#Errores comunes

#400 Bad Request

La validación falló: error.code es invalid_request y error.details enumera todos los campos con error (error.param solo está presente cuando falla exactamente un campo).

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_request",
    "message": "Validation failed",
    "details": [
      { "param": "amount", "message": "must be a positive integer" },
      { "param": "payment_method", "message": "Invalid enum value" }
    ]
  },
  "request_id": "req_01J..."
}

También se devuelve 400 cuando falta el encabezado Idempotency-Key en una escritura financiera. En POST /v1/charges y POST /v1/pix/charges el encabezado es el único canal. En POST /payments se acepta el campo del cuerpo idempotencyKey como alias: si se envían ambos deben coincidir (una discrepancia es un 400) y el valor que cuenta es el del encabezado. POST /payments/{id}/refund es exclusivo del panel de administración, por lo que nunca acepta ninguna de las dos formas desde una clave de API.

Acción Corrige la solicitud y reintenta. Nunca entres en bucle.


#401 Unauthorized

json
{
  "error": {
    "type": "authentication_error",
    "code": "unauthorized",
    "message": "Invalid API key"
  },
  "request_id": "req_01J..."
}

Posibles causas:

  • Falta el encabezado: asegúrate de que Authorization: Bearer lq_live_... esté configurado.
  • La clave fue rotada: revisa el panel.
  • Espacios en blanco: las claves deben coincidir exactamente, sin espacios iniciales ni finales.

Acción Verifica la clave. Si es correcta, puede que la clave se haya desactivado: contacta con soporte.


#403 Forbidden

La protección antifraude/de velocidad bloquea un cobro antes de que llegue al proveedor — error.code es fraud.*:

json
{
  "error": {
    "type": "permission_error",
    "code": "fraud.customer_velocity_exceeded",
    "message": "Too many payment attempts for this customer in a short window"
  },
  "request_id": "req_01J..."
}

Un segundo código fraud.* cubre el límite de velocidad propio de la cuenta (mensaje: "Too many payment attempts from this account in a short window").

Acción Revisa al cliente / reduce la frecuencia de los reintentos. Solo los cobros vigentes cuentan para el límite: los cobros expirados o cancelados (p. ej., un QR de Pix que caducó sin pagarse) se ignoran, así que reintentarlos es seguro. Si el bloqueo parece incorrecto, escribe a suporte@liqfy.com.br indicando el request_id.

#Reembolsos — marcador de mensaje REFUND_ADMIN_ONLY

POST /payments/{id}/refund rechaza a todo llamante con clave de API con 403, sea cual sea el cuerpo: los reembolsos se emiten únicamente desde el panel de administración de Liqfy. Esto no es un valor de code: error.code es el "forbidden" simple y el token sobrevive solo como prefijo del mensaje, así que ramifica según el código para el rechazo y compara el prefijo solo como marcador (p. ej., message.startsWith("REFUND_ADMIN_ONLY")):

json
{
  "error": {
    "type": "permission_error",
    "code": "forbidden",
    "message": "REFUND_ADMIN_ONLY: reembolsos devem ser solicitados pelo painel/admin, ..."
  },
  "request_id": "req_01J..."
}

Consulta Referencia de la API — Reembolsos.

#Lista de IP permitidas

Cuando la cuenta tiene configurada una lista de IP permitidas, una solicitud desde una dirección fuera de ella se rechaza con 403 — mismo sobre permission_error, error.code "forbidden". La comprobación es una protección por cuenta en las rutas principales de la cuenta (billeteras, retiros, transacciones, casos MED, api-keys y rutas similares); las rutas de movimiento de dinero de los servicios de pagos y de webhooks (/v1/charges, /v1/pix/charges, /v1/payments, /v1/webhooks) no están cubiertas por ella. Añade a la lista todas las direcciones de salida que usen tus integraciones (incluidos los hosts de CI y de failover), o la solicitud nunca llegará a la comprobación de la clave. Detalles y cómo actualizar la lista: Claves de API.

#account_suspended

La cuenta está suspendida o bloqueada: las solicitudes con clave de API a cobros, pagos, retiros, billetera y transacciones se rechazan hasta que soporte levante el bloqueo. La clave en sí sigue siendo válida: rotarla no ayuda.

json
{
  "error": {
    "type": "permission_error",
    "code": "account_suspended",
    "message": "Conta suspensa: a API não aceita requisições desta conta. Fale com o suporte."
  },
  "request_id": "req_01J..."
}

Acción No reintentes. Escribe a suporte@liqfy.com.br indicando el request_id.


#404 Not Found

json
{
  "error": {
    "type": "not_found_error",
    "code": "not_found",
    "message": "Charge ch_a1b2c3d4-0000-0000-0000-000000000009 not found"
  },
  "request_id": "req_01J..."
}

Acción Verifica el id. Si acabas de crear el recurso, espera 1–2 segundos y reintenta.


#409 Conflict

json
{
  "error": {
    "type": "conflict_error",
    "code": "idempotency_key_reused",
    "message": "Idempotency key 'ORD-7821' already used with a different request body"
  },
  "request_id": "req_01J..."
}

Repetiste una Idempotency-Key con un cuerpo distinto del original. Liqfy se niega a sobrescribir en silencio.

Otros dos códigos conflict_error llegan con 409:

error.codeCuándoAcción
idempotency_key_reusedMisma clave, cuerpo distinto.Usa una clave nueva: la causa habitual es reutilizar un id de pedido después de que el cliente cambió el carrito.
idempotency_key_in_flightUna primera solicitud con esta clave todavía se está procesando.Espera a que termine (o reintenta tras unos segundos): no cambies de clave.
payment_not_cancellablePOST /payments/{id}/cancel sobre un cobro PROCESSING.Espera a que el cobro se liquide y, en su lugar, reembolsa desde el panel de administración.

#Rechazos por reglas de negocio

Los fallos de forma de la solicitud (campos ausentes/no válidos) son 400, error.type: "invalid_request_error". Algunos fallos de configuración posteriores — por ejemplo, ningún proveedor configurado para el payment_method y el currency solicitados — se transmiten desde el orquestador interno con su propio estado y un código de máquina UPPER_SNAKE_CASE, con mayor frecuencia 422:

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "NO_PROVIDER_CONFIGURED",
    "message": "No payment provider is configured for this payment method and currency."
  },
  "request_id": "req_01J..."
}

Códigos de esta familia del lado de los cobros: PROVIDER_UNAVAILABLE, PROVIDER_ERROR, PAYMENT_REJECTED, NO_PROVIDER_CONFIGURED, UNKNOWN_METHOD, MOCK_PROVIDER_IN_PRODUCTION, PAYER_IDENTITY_REQUIRED (cuando expected_payer_tax_id bloquea el cobro y el documento del pagador no coincide).

Códigos del lado de pagos/retiros: WALLET_NOT_FOUND, WALLET_NOT_SPENDABLE, AMOUNT_BELOW_MINIMUM, AMOUNT_ABOVE_MAXIMUM, DAILY_PAYOUT_LIMIT_EXCEEDED (422), INSUFFICIENT_BALANCE, WITHDRAWAL_NOT_CANCELLABLE. Ten en cuenta la excepción en la ruta de retiros: INSUFFICIENT_BALANCE llega como 400, no como 422: ramifica según error.code, no según el estado.

Acción Lee error.code y message. No reintentes: corrige la entrada o contacta con soporte si la cuenta debería tener un proveedor configurado.


#429 Too Many Requests

json
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limited",
    "message": "Rate limit exceeded"
  },
  "request_id": "req_01J..."
}

Encabezados de esta respuesta (del plugin de rate limiting de Kong, con alcance por clave de API; consulta Primeros pasos §4 para los umbrales actuales):

text
Retry-After: 12
X-RateLimit-Limit-Minute: 5000
X-RateLimit-Remaining-Minute: 0

Acción Reduce el ritmo: respeta Retry-After si está presente; de lo contrario, usa backoff exponencial con jitter.

js
async function withRetry(fn, attempts = 5) {
  for (let i = 0; i < attempts; i++) {
    try { return await fn(); }
    catch (err) {
      if (err.status !== 429 && err.status < 500) throw err;
      const base = Math.min(1000 * 2 ** i, 30_000);
      const jitter = Math.random() * 0.3 * base;
      await new Promise(r => setTimeout(r, base + jitter));
    }
  }
  throw new Error('exhausted retries');
}

#5xx — del lado del servidor

json
{
  "error": {
    "type": "api_error",
    "code": "service_unavailable",
    "message": "Upstream unavailable"
  },
  "request_id": "req_01J..."
}

Es seguro reintentar. Usa la misma Idempotency-Key para que nunca se cree un cobro duplicado si el procesamiento realmente tuvo éxito pero la respuesta se perdió.

#Errores de entrega de webhooks (servidor → tu endpoint)

Cuando Liqfy no puede alcanzar tu endpoint de webhook, el fallo aparece en GET /v1/webhooks/deliveries:

json
{
  "id": "b2c3d4e5-f6a7-4890-9abc-def012345678",
  "status": "FAILED",
  "attempts": 3,
  "maxAttempts": 15,
  "lastStatusCode": 502,
  "lastError": "HTTP 502",
  "lastResponseBody": "Bad Gateway",
  "nextRetryAt": "2026-07-23T16:08:00.000Z"
}
lastStatusCode / lastErrorQué significa
2xxEntregado. No se reintentará.
4xxTu endpoint rechazó el payload. Liqfy sigue reintentando hasta maxAttempts: corrige y repite si es necesario.
5xxTu endpoint está caído. Liqfy reintenta con backoff exponencial.
429 / Retry-AfterLiqfy respeta tu Retry-After y reprograma el envío.
Connection timeout / ETIMEDOUTTu endpoint tardó >30 s en responder. Responde siempre 200 OK rápido y encola el procesamiento de forma asíncrona.
getaddrinfo ENOTFOUNDTu dominio no resuelve. Actualiza la URL registrada.
self signed certificateError de TLS. Liqfy requiere certificados públicos válidos.

Tras maxAttempts (por defecto 15), la entrega se envía a la DLQ y puede repetirse manualmente. Consulta Webhooks para la semántica completa de reintentos/DLQ.

#Errores de gestión de webhooks

Códigos en las rutas de administración de /v1/webhooks (permission_error / conflict_error / rate_limit_error según el código):

error.codeCuándo
WEBHOOK_ENDPOINT_DUPLICATELa URL ya está registrada en esta cuenta (409).
WEBHOOK_ENDPOINT_LIMITLa cuenta alcanzó su límite de endpoints activos.
WEBHOOK_HTTPS_REQUIREDUna URL que no es https:// fuera de sandbox/staging.
WEBHOOK_OWN_DOMAINLa URL apunta a un dominio Liqfy.
WEBHOOK_TEST_RATE_LIMITDemasiadas llamadas a POST /endpoints/{id}/test en una hora (429).
WEBHOOK_ROLE_REQUIREDEl rol del llamante no puede gestionar endpoints (access.canManage: false).
IMPERSONATION_VIEW_ONLYLa sesión está en modo de suplantación y solo puede leer.
TENANT_NOT_ACTIVELa cuenta no está activa.

#Decodificación de error.details

error.details es un arreglo plano de { param, message } que se puede renderizar tal cual:

jsx
{error.details.map(({ param, message }) => (
  <li key={param}>{param}: {message}</li>
))}

El alias fieldErrors es la misma información reestructurada como { field: reasons[] }:

json
{
  "fieldErrors": {
    "amount": ["must be a positive integer"]
  }
}

#Cuándo escribir a soporte

Contacta con suporte@liqfy.com.br (o abre un ticket) si:

  • Ves un 5xx que persiste más de 5 minutos
  • Una entrega de webhook está atascada en FAILED después de maxAttempts
  • Llega un 403 Forbidden de forma inesperada
  • La misma Idempotency-Key devuelve cobros distintos en llamadas distintas (es un bug: nunca debería ocurrir)

Cita siempre el request_id: nos permite rastrear cada log, span y mensaje de Kafka en una sola consulta.

#Límite de lectura por entidad

HTTP 429, error.type: rate_limit_error, error.code: entity_rate_limited. Espera los segundos indicados por Retry-After antes de volver a leer esta entidad. Consulta Polling y límites.