API
Solicita acceso →
Referencia / Pagos

Pagos

El recurso Pagos es el corazón de Alibanca: es donde de verdad mueves dinero. Cada vez que emites un pago le estás pidiendo a la infraestructura que dispersar un monto a un destino (un teléfono de pago móvil o una cuenta) con garantías de grado bancario. En esta página encontrarás los cuatro endpoints del recurso, campo por campo, con ejemplos completos de request y respuesta, la explicación del destino excluyente (beneficiaryId vs rail+beneficiary), los estados de un pago y las buenas prácticas para que tu integración nunca pague dos veces ni pierda el rastro de un centavo.

Los cuatro endpoints de un vistazo

El recurso Pagos vive bajo la ruta /v2/payments más un endpoint hermano, /v2/balance, para consultar tu saldo. Antes de entrar al detalle, aquí tienes el índice: emitir un pago, consultar el estado de uno, listar los que ya emitiste y ver cuánto saldo tienes disponible.

POST/v2/paymentsEmitir un pago (dispersar dinero a un destino)
GET/v2/payments/:idConsultar el estado de un pago por su id
GET/v2/paymentsListar tus pagos con paginación keyset
GET/v2/balanceVer el saldo disponible de tu cuenta
i
Todo va bajo /v2. En sandbox la base es https://alibanca-api-production.up.railway.app y en producción https://api.alibanca.com. El sandbox corre sobre un proveedor simulado: puedes emitir todos los pagos que quieras porque no mueve un centavo real. Es el lugar perfecto para practicar.

Cómo funciona un pago, en cristiano

Un pago en Alibanca es una orden de dispersión: tú dices "mueve este monto a este destino" y la infraestructura se encarga del resto contra el banco. Piénsalo como cuando le das a un cajero una boleta de depósito ya llena: el cajero (Alibanca) valida, ejecuta y te da un comprobante con un estado. La diferencia es que todo pasa por API y con protecciones de dinero pensadas para que nunca pagues dos veces.

Cada pago que emites nace con tres piezas de seguridad que ya deberías conocer de la sección de Autenticación →: la firma (x-signature) que prueba que la orden salió de ti y no fue alterada; la idempotency-key que hace que reintentar sea seguro; y el nonce creciente dentro del envelope firmado que evita que alguien reproduzca una orden vieja. No necesitas volver a explicarlas aquí, pero sí tenerlas presentes: todo POST /v2/payments las lleva.

El destino: beneficiaryId o rail+beneficiary, nunca ambos

Un pago necesita saber a dónde va el dinero, y tienes exactamente dos maneras de decirlo. La regla es un XOR (una o exclusiva): eliges una vía o la otra, jamás las dos al mismo tiempo, y jamás ninguna.

Vía 1 — un beneficiario guardado (beneficiaryId). Si ya guardaste el destino con POST /v2/beneficiaries →, solo pasas su beneficiaryId. El destino real (riel, cuenta, documento) quedó congelado al momento de guardarlo, así que el pago hereda ese destino inmutable. Es la vía recomendada para destinos recurrentes: reduces la superficie de error humano porque no re-tecleas la cuenta en cada pago.

Vía 2 — destino en línea (rail + beneficiary). Si es un destino de una sola vez o aún no lo has guardado, mandas el riel (rail: "PM" para pago móvil o "CCE" para transferencia por cámara de compensación) junto con el identificador del destino (beneficiary: el teléfono si es PM, o la cuenta si es CCE).

!
Es excluyente de verdad. Mandar beneficiaryId y rail/beneficiary en el mismo request es un error de validación (400), no una preferencia. Igual que mandar solo rail sin beneficiary. Decide la vía antes de armar el cuerpo.

POST /v2/payments — Emitir un pago

POST/v2/payments

Crea y ejecuta un pago. Este es el único endpoint del recurso que mueve dinero, así que es también el que más protecciones exige: firma, idempotency-key y nonce creciente, todo como se describe en Autenticación →. El cuerpo es JSON y define el monto, la moneda y el destino (por una de las dos vías del XOR).

Parámetros del cuerpo

CampoTipoDescripción
amountMinorreqstringEl monto a mover, expresado en unidades menores (céntimos) como string, nunca como número. Por ejemplo, "150000" son 1.500,00 VES. Se usa string para que jamás entre un float y pierdas precisión al mover dinero: los flotantes redondean, y en dinero eso es inaceptable.
currencyreqstringLa moneda del pago. Hoy el valor soportado es "VES" (bolívares). Otras monedas llegan próximamente. Debe coincidir con la moneda del destino y de tu saldo.
beneficiaryIdstringEl id de un beneficiario que ya guardaste. Su destino está congelado (riel/cuenta/documento inmutables). Excluyente con rail+beneficiary: usas esta vía o la otra, nunca ambas.
railstringEl riel por donde viaja el dinero cuando das el destino en línea: "PM" (pago móvil) o "CCE" (transferencia por cámara de compensación). Va siempre acompañado de beneficiary. Excluyente con beneficiaryId.
beneficiarystringEl identificador del destino en línea: el teléfono si rail es "PM", o la cuenta si es "CCE". Va siempre junto a rail. Excluyente con beneficiaryId.
i
La idempotency-key no va en el cuerpo. Va en el header idempotency-key. Escoge un valor único y determinístico por operación lógica (por ejemplo, el id de la orden en tu sistema). Si reintentas el mismo pago con la misma llave, Alibanca te devuelve el mismo resultado en vez de crear un pago nuevo.

Ejemplo — emitir un pago por pago móvil

Este pago manda 1.500,00 VES ("150000" céntimos) a un teléfono de pago móvil, dando el destino en línea con rail+beneficiary. Fíjate en los headers: la llave, la firma del envelope y la idempotency-key.

cURLNodeCopiar
curl -X POST https://alibanca-api-production.up.railway.app/v2/payments \
  -H 'x-api-key: sk_test_TU_LLAVE' \
  -H 'x-signature: <firma-RSA-del-envelope>' \
  -H 'idempotency-key: orden-8842' \
  -H 'Content-Type: application/json' \
  -d '{"amountMinor": "150000", "currency": "VES", "rail": "PM", "beneficiary": "04141234567"}'

Si todo sale bien y el banco confirma de inmediato, la respuesta llega con state: "completed". El id que ves en el payload es el que usarás luego para consultar el estado o cuadrar tus registros.

RESPUESTA 201 application/json
{
  "status": "success",
  "traceId": "trc_9f2a1c7b3e",
  "payload": {
    "id": "pay_01HZX8K3Q2M9",
    "state": "completed",
    "subStatus": "confirmed",
    "amountMinor": "150000",
    "currency": "VES",
    "rail": "PM",
    "beneficiary": "04141234567"
  }
}
Pruébalo. En sandbox, el céntimo del monto dispara escenarios para probar tu código de producción sin riesgo. Un monto que termina en .02 (ej. "150002") devuelve unknown. Uno que termina en .03 (ej. "150003") "mueve" el dinero pero igual devuelve unknown — es el trap del doble-pago: sirve para verificar que tu integración no re-emite. Cualquier otro monto termina en completed.

Los estados de un pago

Todo pago cae en uno de tres estados en el campo state, refinado por subStatus. Entender la diferencia entre ellos es lo que separa una integración segura de una que paga doble.

completed unknown failed

completed — el dinero se movió y el banco lo confirmó. Puedes marcar la operación como pagada.

failed — el pago no se ejecutó y Alibanca te garantiza que no hubo débito. Es seguro reintentar, pero con una idempotency-key NUEVA, porque es una operación lógica distinta.

unknown — el estado de primera clase que hace a Alibanca diferente. Significa "el banco no respondió a tiempo (timeout o caída), así que no sabemos todavía si el dinero se movió; lo estamos reconciliando". No es un error: es honestidad.

!
La regla de oro del unknown. Un pago en unknown NUNCA se reintenta y NUNCA se re-emite como pago nuevo. Se resuelve solo, por conciliación de saldo. Si emites un pago nuevo sobre un unknown, corres el riesgo de pagar dos veces. Solo failed autoriza reintentar (con idempotency-key nueva).

¿Y quién resuelve un unknown? La conciliación (recon). Alibanca cuadra continuamente el saldo real del banco contra su ledger de doble partida y detecta débitos sin dueño (huérfanos), créditos sin fondeo, doble-débitos byte-idénticos y payouts confirmados sin débito real. En criollo: lo caza la conciliación antes que tú. Tú solo tienes que no re-emitir mientras el estado sea unknown.

GET /v2/payments/:id — Consultar el estado de un pago

GET/v2/payments/:id

Devuelve el estado actual de un pago por su id. Es la vía de poll: consultas cuando quieras para saber en qué anda un pago, especialmente uno que quedó en unknown y luego se resuelve por conciliación. Si además configuraste webhooks →, recibirás el cambio de estado empujado; el poll y el webhook son complementarios.

Parámetro de ruta

CampoTipoDescripción
idreqstringEl identificador del pago, tal como lo devolvió POST /v2/payments en payload.id (por ejemplo pay_01HZX8K3Q2M9). Va en la URL, no en el cuerpo.
cURLNodeCopiar
curl https://alibanca-api-production.up.railway.app/v2/payments/pay_01HZX8K3Q2M9 \
  -H 'x-api-key: sk_test_TU_LLAVE' \
  -H 'x-signature: <firma-RSA-del-envelope>'

La respuesta trae el pago con su estado más reciente. Aquí un ejemplo de un pago que aún no se sabe si movió el dinero — el famoso unknown — con su subStatus indicando que está en conciliación.

RESPUESTA 200 application/json
{
  "status": "success",
  "traceId": "trc_44b0e9a2f1",
  "payload": {
    "id": "pay_01HZX8K3Q2M9",
    "state": "unknown",
    "subStatus": "reconciling",
    "amountMinor": "150002",
    "currency": "VES",
    "rail": "PM",
    "beneficiary": "04141234567"
  }
}
i
Un unknown no se queda ahí para siempre. La conciliación lo empuja hacia completed o failed cuando el saldo del banco cuadra. Si haces poll a este endpoint, verás el estado cambiar solo. Mientras tanto: no re-emitas.

GET /v2/payments — Listar tus pagos

GET/v2/payments

Lista los pagos que has emitido, del más reciente al más viejo. Usa paginación keyset (también llamada paginación por cursor) sobre un id descendente, no offset. La diferencia importa: con offset, si se insertan pagos entre página y página, puedes saltarte o repetir registros; con keyset le dices "dame lo que viene después de este id que ya vi", y el listado se mantiene consistente aunque lleguen pagos nuevos.

Cómo iteras las páginas

La primera llamada te trae la página más reciente. Tomas el último id de esa página y se lo pasas a la siguiente llamada para pedir "lo que sigue hacia atrás". Repites hasta que la respuesta ya no traiga más pagos. Así de simple: es un bucle que avanza con el último id visto.

CampoTipoDescripción
startingAfterstringEl id del último pago que viste en la página anterior. Al pasarlo, recibes la siguiente página hacia atrás (ids menores). Omítelo en la primera llamada para empezar por lo más reciente.
limitnumberCuántos pagos quieres por página. Es opcional; si no lo mandas, se usa un tamaño por defecto. Sirve para controlar el peso de cada respuesta.
cURLNodeCopiar
# Primera página (la más reciente)
curl https://alibanca-api-production.up.railway.app/v2/payments?limit=2 \
  -H 'x-api-key: sk_test_TU_LLAVE' \
  -H 'x-signature: <firma-RSA-del-envelope>'

# Siguiente página: pasas el ultimo id que viste
curl https://alibanca-api-production.up.railway.app/v2/payments?limit=2&startingAfter=pay_01HZX8K3Q2M9 \
  -H 'x-api-key: sk_test_TU_LLAVE' \
  -H 'x-signature: <firma-RSA-del-envelope>'

El payload es un arreglo de pagos ordenado por id descendente. Cuando el arreglo vuelva vacío, ya recorriste todo el historial.

RESPUESTA 200 application/json
{
  "status": "success",
  "traceId": "trc_7ac1d0e5b8",
  "payload": [
    {
      "id": "pay_01HZXB7N4T2P",
      "state": "completed",
      "subStatus": "confirmed",
      "amountMinor": "48000",
      "currency": "VES",
      "rail": "CCE",
      "beneficiary": "01020304050600070809"
    },
    {
      "id": "pay_01HZX8K3Q2M9",
      "state": "completed",
      "subStatus": "confirmed",
      "amountMinor": "150000",
      "currency": "VES",
      "rail": "PM",
      "beneficiary": "04141234567"
    }
  ]
}
i
Por qué keyset y no offset. Offset (?page=3) le pide a la base "sáltate 40 y dame 20": si entran pagos nuevos mientras paginas, los conteos se corren y ves duplicados o huecos. Keyset ancla en un id real, así que la ventana es estable aunque el mundo cambie debajo de ti. Es el estándar para listados que crecen en vivo.

GET /v2/balance — Ver tu saldo disponible

GET/v2/balance

Devuelve el saldo disponible de tu cuenta: cuánto dinero puedes mover ahora mismo. Como todo monto en Alibanca, viene en unidades menores (céntimos) y como string, para que nunca lo trates como float. Consúltalo antes de emitir un lote grande de pagos, o para reconciliar contra tus propios registros.

cURLNodeCopiar
curl https://alibanca-api-production.up.railway.app/v2/balance \
  -H 'x-api-key: sk_test_TU_LLAVE' \
  -H 'x-signature: <firma-RSA-del-envelope>'
RESPUESTA 200 application/json
{
  "status": "success",
  "traceId": "trc_2e6b9f0a41",
  "payload": {
    "currency": "VES",
    "availableMinor": "48250000"
  }
}

En el ejemplo, "48250000" son 482.500,00 VES disponibles. Recuerda dividir entre 100 solo para mostrar: internamente sigue conviviendo con el resto de tu contabilidad en céntimos, sin flotantes de por medio.

Errores comunes y cómo evitarlos

La mayoría de los tropiezos con el recurso Pagos no son del banco: son del cuerpo o de los headers. Aquí los más frecuentes, para que los caches antes de que te caches a ti.

Mandar el destino por las dos vías. Si pones beneficiaryId junto con rail/beneficiary, es 400. Elige una sola vía por pago.

Montos como número. amountMinor es string. Un 150000 sin comillas puede perder precisión en algún punto de tu stack; usa siempre "150000".

Confundir mayor y menor. Un pago de 1.500,00 VES es "150000" céntimos, no "1500". Multiplica por 100 antes de enviar.

Reintentar un unknown. No lo hagas. Reintentar solo aplica a failed, y siempre con idempotency-key nueva. Sobre un unknown nunca se crea un pago nuevo.

Reutilizar la idempotency-key para operaciones distintas. La llave identifica una operación lógica. Si reciclas la misma llave para otro pago, recibirás el resultado del primero, no un pago nuevo.

Buenas prácticas para mover dinero con tranquilidad

1

Guarda destinos recurrentes como beneficiarios

Si vas a pagarle varias veces al mismo destino, guárdalo con POST /v2/beneficiaries → y paga con beneficiaryId. El destino queda congelado e inmutable, así que eliminas el riesgo de teclear mal una cuenta en cada pago.

2

Deriva idempotency-keys de tu propio id de orden

Usa algo determinístico de tu sistema (por ejemplo orden-8842). Así, si tu proceso se cae a mitad y reintenta, la misma orden vuelve a mandar la misma llave y Alibanca te devuelve el mismo pago, no uno nuevo.

3

Trata unknown como "esperar", no como "reintentar"

Cuando veas unknown, marca la operación como pendiente y haz poll a GET /v2/payments/:id (o espera el webhook). La conciliación lo resolverá a completed o failed. No crees un pago nuevo.

4

Ejercita los montos mágicos en sandbox

Antes de ir a producción, dispara a propósito un .02 y un .03 en sandbox y verifica que tu código no re-emite ante un unknown. El .03 es la trampa del doble-pago: si tu integración la pasa, estás listo.

5

Combina poll y webhook

Configura webhooks → para enterarte de los cambios de estado sin estar preguntando, y deja el poll como respaldo. Los webhooks van firmados con RSA asimétrica y los verificas con el JWKS público, sin secreto compartido.

Pruébalo de punta a punta. En sandbox: emite un pago normal (→ completed), luego uno que termine en .02 (→ unknown), haz poll a GET /v2/payments/:id y observa cómo la conciliación lo resuelve. Todo sin mover un centavo real. Cuando tu flujo maneje bien el unknown, estás a un cambio de llave (sk_live_…) de producción.