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.
/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).
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
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
| Campo | Tipo | Descripción |
|---|---|---|
| amountMinorreq | string | El 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. |
| currencyreq | string | La 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. |
| beneficiaryId | string | El 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. |
| rail | string | El 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. |
| beneficiary | string | El 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. |
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.
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.
{ "status": "success", "traceId": "trc_9f2a1c7b3e", "payload": { "id": "pay_01HZX8K3Q2M9", "state": "completed", "subStatus": "confirmed", "amountMinor": "150000", "currency": "VES", "rail": "PM", "beneficiary": "04141234567" } }
.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 — 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.
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
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
| Campo | Tipo | Descripción |
|---|---|---|
| idreq | string | El 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. |
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.
{ "status": "success", "traceId": "trc_44b0e9a2f1", "payload": { "id": "pay_01HZX8K3Q2M9", "state": "unknown", "subStatus": "reconciling", "amountMinor": "150002", "currency": "VES", "rail": "PM", "beneficiary": "04141234567" } }
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
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.
| Campo | Tipo | Descripción |
|---|---|---|
| startingAfter | string | El 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. |
| limit | number | Cuá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. |
# 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.
{ "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" } ] }
?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
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.
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>'
{ "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
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.
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.
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.
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.
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.
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.