API
Solicita acceso →
Conceptos / Sandbox y montos mágicos

Sandbox y montos mágicos

El sandbox es una copia idéntica de Alibanca donde puedes emitir pagos, listar movimientos y recibir webhooks sin mover un solo centavo real. Corre el mismo código que producción, pero detrás usa un proveedor simulado. Su superpoder son los montos mágicos: eliges el último céntimo del monto y el sandbox te devuelve, a propósito, el escenario que quieres ensayar — desde un pago exitoso hasta un rechazo, un timeout del banco o el temido doble-pago — para que pruebes tu integración contra cada falla antes de tocar dinero de verdad.

Cómo funciona el sandbox

Un sandbox (literalmente "caja de arena") es un entorno de pruebas: un lugar donde puedes romper cosas sin consecuencias. En el mundo de los pagos esto no es un lujo, es una necesidad. Cuando tu código mueve dinero, un bug no te da un error rojo en la pantalla y ya — te deja un débito real que alguien tiene que reversar a mano. El sandbox existe para que todos esos bugs los descubras aquí, gratis, y no en producción con dinero de tus usuarios.

Detrás de escena, el sandbox de Alibanca usa lo que llamamos el MockProvider (proveedor simulado). Un "proveedor" es la pieza que, en producción, se conecta con el banco de verdad para ejecutar la dispersión. El MockProvider ocupa ese mismo lugar, pero en vez de hablar con un banco real, finge las respuestas siguiendo reglas deterministas que tú controlas con el monto. Piensa en un simulador de vuelo: los mandos, las pantallas y los procedimientos son idénticos a los de un avión real; lo único que cambia es que si te estrellas, nadie sale herido.

i
Cero dinero real, siempre. El sandbox nunca mueve fondos. No importa qué monto envíes ni qué beneficiario uses: no hay débito, no hay transferencia, no hay nada que reversar. Puedes emitir mil pagos de prueba sin pensarlo dos veces.

La regla más importante que debes entender es esta: el sandbox corre exactamente el mismo código que producción. La lógica de firma, la idempotencia, la conciliación, la máquina de estados de los pagos, la validación de campos — todo es bit por bit lo mismo. Lo único que se intercambia es el proveedor final: MockProvider en sandbox, el proveedor bancario real en producción. Esto es clave, porque significa que si tu integración funciona en sandbox, funciona en producción. No hay sorpresas escondidas en la diferencia entre ambos entornos, porque casi no hay diferencia.

Pide tu llave de sandbox (sk_test_)

Para hablar con la API necesitas una llave de API (API key): un secreto que identifica a tu cuenta en cada request. Alibanca usa dos tipos de llave, y las distingues por su prefijo:

PrefijoEntornoQué hace
sk_test_SandboxApunta al MockProvider. No mueve dinero real. Úsala para todo tu desarrollo y tus pruebas.
sk_live_ProducciónApunta al proveedor bancario real. Mueve dinero de verdad. Solo se emite cuando tu integración está lista y verificada.

El "sk" viene de secret key — llave secreta. El nombre lo dice todo: es un secreto, y vive solo del lado del servidor. Nunca la pongas en una app móvil, en el JavaScript de un navegador, en un repositorio público ni en un log. Cualquiera que tenga tu sk_test_ puede emitir pagos de prueba en tu nombre; cualquiera que tenga tu sk_live_ puede mover tu dinero real.

Tu llave de sandbox sk_test_… te la entrega Alibanca cuando das de alta tu cuenta de desarrollo. Junto a ella, registras tu llave pública RSA: Alibanca guarda esa llave pública para verificar la firma de cada request, mientras tu llave privada nunca sale de tu servidor. Este par de llaves es lo que hace que cada request sea infalsificable. (El detalle completo de cómo firmar vive en Autenticación y firma →; aquí solo te importa saber que la sk_test_ es la puerta de entrada al sandbox.)

!
No mezcles entornos. Una sk_test_ solo funciona contra la base de sandbox y una sk_live_ solo contra producción. Si envías una llave de test a la URL de producción (o al revés), el request es rechazado. Es una barrera a propósito: hace imposible que un pago de prueba se cuele a producción por accidente.

Todo lo que recibes, en un solo bloque

Esto es lo que necesitas para hacer tu primer request. Pégalo en tu archivo de variables de entorno y sustituye los dos valores que te entrega Alibanca:

.envCopiar
# Alibanca — sandbox
ALIBANCA_BASE_URL=https://alibanca-api-production.up.railway.app
ALIBANCA_KEY=sk_test_...

# La llave privada va como RUTA A UN ARCHIVO, nunca pegada aquí adentro:
# un .env se copia en logs, en imágenes de contenedor y en capturas de pantalla.
# Guárdala fuera de tu repositorio y con permisos 600.
ALIBANCA_PRIVATE_KEY_FILE=/ruta/segura/fuera-del-repo/privada.pem
i
No hay client secret ni token que renovar. Alibanca no usa OAuth: no tienes que pedir un access token, ni guardarlo, ni refrescarlo antes de que expire. Cada request se firma con tu llave privada en el momento. Son tres variables y ninguna caduca.

Antes de firmar nada: confirma la conexión

Firmar un request es lo más delicado de la integración, así que antes de pelearte con la firma conviene comprobar que le estás pegando al servidor correcto. Para eso existe GET /v2/version, un endpoint público: no requiere llave ni firma. Es tu "ping" de arranque. Si te responde, sabes que la URL es correcta, que hay conexión y en qué entorno estás parado.

GET/v2/version
cURLNodeCopiar
curl https://alibanca-api-production.up.railway.app/v2/version
RESPUESTA 200 application/json
{
  "version": "v0.13.0",
  "apiVersion": "v2",
  "environment": "sandbox",
  "rails": ["PM", "CCE"],
  "simulado": true,   // en sandbox el proveedor es simulado
  "links": {
    "openapi": "/v2/openapi.json",
    "jwks": "/v2/webhook/keys"
  }
}

Fíjate en el campo environment: si dice "sandbox", estás en el entorno seguro. Es la forma más rápida de confirmar, de un vistazo, que no vas a mover dinero real. El campo rails te dice qué rieles de dispersión están disponibles — PM (pago móvil) y CCE (transferencia interbancaria) — y links te apunta al contrato completo de la API (openapi.json) y a las llaves públicas con las que se verifican los webhooks (jwks).

i
Empieza siempre por aquí. Si GET /v2/version no responde, no tiene sentido depurar tu firma: el problema es de red o de URL, no de tu código. Descarta lo simple primero.

Montos mágicos: el céntimo que dispara el escenario

Aquí está la parte divertida. En un sandbox normal, todos los pagos "salen bien" y ya. El problema es que en el mundo real los pagos no siempre salen bien: el banco a veces tarda, a veces se cae a mitad de la operación, a veces confirma tarde. Tu integración tiene que saber comportarse ante cada uno de esos casos — y no puedes probarlos si el sandbox solo sabe decir "éxito".

La solución de Alibanca son los montos mágicos. La idea es simple: el último céntimo del monto que envías le indica al MockProvider qué escenario simular. Como el monto lo controlas tú en cada request, tienes un interruptor para pedir a voluntad un éxito, un timeout o una trampa de doble-pago. Es el mismo truco que usan las tarjetas de prueba de otros proveedores de pagos (donde ciertos números disparan un rechazo), pero aplicado al monto.

Recuerda que en Alibanca el dinero se expresa en unidades menores (céntimos) y como string, en el campo amountMinor. Así que "150000" son 1.500,00 VES, y "150002" son 1.500,02 VES. Ese 02 final es lo que dispara la magia.

El monto termina en…Estado que devuelveQué escenario ensayas
.02unknownEl banco no respondió a tiempo (timeout o caída). Alibanca no sabe todavía si el dinero se movió o no; lo deja en unknown y lo resuelve por conciliación. Practica tu manejo del estado indeterminado.
.03unknownEl dinero sí se movió del lado del banco, pero la respuesta se perdió y el estado igual vuelve unknown. Es el trap del doble-pago: comprueba que tu integración NO re-emite el pago al ver el unknown.
.01failedFondos insuficientes en la cuenta pagadora. Rechazo limpio: garantizamos que no hubo débito, así que es seguro reintentar — con una idempotency-key NUEVA, porque es un intento genuinamente distinto, y corrigiendo antes la causa que indica el subStatus.
.07unknown → failedSólo en el riel CCE. Igual que .06: el pago va en vuelo a la cámara. Pero esta vez la cámara lo rechaza, y el dinero vuelve. Tu pago pasa de unknown a failed después de que consultes. Es el escenario que prueba que NO reemitiste mientras esperabas: si lo hiciste, ahora tienes dos pagos y sólo uno rebotó. En pago móvil este monto liquida normal.
.06unknownSólo en el riel CCE. El riel confirmó tener el pago y lo mandó a la cámara de compensación: va en vuelo, no liquidado. Te llega como unknown con subStatus: "at_rail", y se resuelve solo cuando la cámara confirma. Ensaya tu loop de consulta — y que NO reemites mientras tanto, porque en vuelo el dinero puede llegar. En pago móvil este monto liquida normal: ese riel no produce este estado.
.04se reconciliaEl banco reporta la operación como duplicada (ya la había recibido). No se reintenta: se concilia contra la que ya existe. Ensaya que tu código no interpreta un duplicado como una falla.
.05failedBeneficiario restringido o inválido: la cuenta destino no admite el pago. Rechazo sin débito, pero reintentar con el mismo destino no va a funcionar — hay que corregir los datos del beneficiario.
.09failedEl monto excede el límite permitido. Rechazo sin débito. Útil para ensayar cómo le muestras un límite a tu usuario final.
cualquier otro céntimocompletedCamino feliz — son los 94 restantes. El pago se confirma de inmediato. Úsalo para el flujo normal.
?
Estos seis son todos. No hay disparadores sin documentar: cualquier otro céntimo liquida. Lo decimos explícito porque lo contrario es una trampa — si .01 rechazara sin estar publicado, un monto cualquiera terminado en 01 te daría un rechazo inexplicable durante una prueba. Un escenario que el sandbox implementa y la doc esconde no protege nada; solo te hace perder una tarde.

Nota fina pero importante: los dos escenarios de falla terminan en el mismo estado visible, unknown. Eso es a propósito. Desde afuera, tu integración no puede distinguir un timeout donde no pasó nada (.02) de uno donde el dinero sí voló (.03). Por eso la regla es tan tajante: ante un unknown, no adivinas — esperas la conciliación. El .03 existe precisamente para castigar a las integraciones que "asumen" y reintentan.

Pero no terminan igual, y conviene que sepas qué esperar. En .03 el dinero sí se movió, así que la recuperación lo encuentra y cierra el pago en completed — solo, sin que reenvíes nada. En .02 no hay nada que encontrar. Pero no se escala al primer intento: un «no encontré registros» puede significar «todavía no lo indexé», así que Alibanca vuelve a preguntar durante un presupuesto de espera y recién después escala. El pago queda en unknown con subStatus: "reconciling" mientras se re-consulta, y pasa a "under_review" cuando el presupuesto se agota y lo toma una persona. Eso no es una falla del sandbox, es la respuesta honesta — la alternativa sería adivinar sobre dinero, que es justo lo que unknown existe para evitar.

La regla de oro: unknown no es failed

El campo state (acompañado de un subStatus con más detalle) expone cinco valores. Dos son de tránsito y tres son finales:

pending processing completed unknown failed

pending es "aceptado y encolado, todavía no fue al banco"; processing (con subStatus: "reserved") es "en vuelo, con los fondos retenidos". Los vas a ver antes de que el pago termine, así que ramificar solo en los tres finales deja dos casos sin manejar.

De los tres finales, la diferencia entre unknown y failed es la lección más cara del negocio de mover dinero, así que grábatela:

1

completed = el dinero llegó

La operación se confirmó de punta a punta. No hay nada más que hacer. El beneficiario recibió sus fondos.

2

failed = NO hubo débito, garantizado

El pago no se ejecutó y Alibanca te garantiza que no salió dinero. Es seguro reintentar — pero con una idempotency-key NUEVA, porque estás iniciando un intento genuinamente nuevo, y corrigiendo antes la causa que indica el subStatus.

3

unknown = no sabemos todavía

El banco no respondió (timeout o caída). El dinero pudo haberse movido o no. Alibanca lo está reconciliando contra el saldo real del banco. NUNCA reintentas un unknown. Nunca creas un pago nuevo encima de él. Esperas.

!
La regla de oro. failed garantiza cero débito → seguro reintentar con idempotency-key nueva, corrigiendo antes la causa que indica el subStatus. unknown = indeterminado → jamás se reintenta ni se crea un pago nuevo encima; se resuelve por conciliación de saldo sin que tú hagas nada. Confundir estos dos estados es la forma número uno de pagar dos veces.

at_rail: el banco confirmó tenerlo, y la regla no cambia

En rieles asíncronos —una transferencia interbancaria— el banco responde «lo recibí, lo estoy procesando» antes de liquidar. Ahí el pago queda en unknown con subStatus: "at_rail".

Es más información que un unknown pelado: sabemos que el banco lo tiene, no solo que no contestó. Pero el state sigue siendo unknown a propósito, porque lo que tú tienes que hacer es exactamente lo mismo: no reemitir. Un pago que va en camino es justo el que más caro sale duplicar.

reconcilingat_rail
Qué sabemosNada: no hubo respuestaEl riel confirmó que lo recibió
Qué haces túEsperarEsperar
ReemitirNuncaNunca

Existe para no despertar a un humano por cada transferencia normal: antes, un interbancario en vuelo se escalaba a revisión manual solo por tardar.

?
Un state que no conoces se trata como unknown, nunca como failed. El enum va a crecer, y queremos poder agregar valores sin romperte la integración. Para eso tu código necesita un caso por defecto: CONOCIDOS.includes(p.state) ? p.state : 'unknown'. Caer en unknown es conservador y no cuesta nada — esperas la conciliación. Caer en failed es lo que cuesta plata, porque failed es el único estado que promete que no hubo débito, y prometerlo sobre un valor que no entendiste es exactamente cómo se paga dos veces.

El unknown es un estado de primera clase en Alibanca, no un error disfrazado. Muchas APIs esconden esta ambigüedad: te devuelven un error genérico y te dejan a ti la pesadilla de decidir si reintentar. Alibanca la nombra explícitamente: "no sé todavía, dame un momento y te reconcilio". Tu trabajo como integrador es respetar ese "no sé" y no forzar la mano.

Ejemplo completo: forzar un unknown con 150002

Vamos a ensayar el timeout del banco. Emitimos un pago por 1.500,02 VES — amountMinor = "150002", que termina en .02 — hacia un pago móvil. Como cualquier pago, va firmado y con su idempotency-key.

POST/v2/payments
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-SHA256 del envelope>" \
  -H "x-nonce: 1721683200123456" \
  -H "idempotency-key: ensayo-unknown-2026-07-22-001" \
  -H "Content-Type: application/json" \
  -d '{"amountMinor":"150002","currency":"VES","rail":"PM","bankCode":"0138","docType":"V","docNumber":"12345678","beneficiary":"04141234567"}'
RESPUESTA 201 application/json
{
  "status": "success",
  "traceId": "trc_9f2c1a7e4b8d",
  "payload": {
    "intentId": "4903",
    "amountMinor": "150002",
    "currency": "VES",
    "rail": "PM",
    "state": "unknown",
    "subStatus": "reconciling"
  }
}

Perfecto: el sandbox simuló que el banco no respondió y te devolvió state: "unknown". Ahora viene lo que de verdad estás probando — cómo reacciona tu código a ese estado. Lo correcto es consultar el pago con GET /v2/payments/:id (poll) y/o esperar el webhook, sin re-emitir nunca. Verifica el estado por su id:

cURLNodeCopiar
curl https://alibanca-api-production.up.railway.app/v2/payments/4877 \
  -H "x-api-key: sk_test_tu_llave" \
  -H "x-signature: <firma del GET>" \
  -H "x-nonce: 1721683200123456"

Mientras la conciliación no cierre, seguirás viendo unknown. Cuando Alibanca cuadre el saldo del banco contra su ledger, el estado migrará solo a completed o a failed, y si tienes webhooks configurados recibirás el evento. Tu integración no tiene que hacer nada más que esperar y escuchar.

Qué verificar en tu integración ante este unknown

  • Que NO re-emitas. El pecado capital es ver unknown, asumir que "algo salió mal" y disparar un POST /v2/payments nuevo. Eso es exactamente lo que produce un doble-pago. Tu código debe tratar unknown como "en curso", no como "falló".
  • Que tu poll sea paciente. Consulta el estado con GET /v2/payments/:id en intervalos razonables (no en un bucle apretado) y déjalo resolver. No pongas un timeout artificial que "cancele" el pago — desde tu lado no puedes cancelar un unknown.
  • Que tu UI no mienta. Si le muestras algo al usuario final, "en proceso" es la verdad. No le digas "falló" (podría haberse pagado) ni "listo" (podría no haberse pagado).
  • Que el idempotency-key quede guardado. Aunque aquí no vas a reintentar, tener el key asociado a la operación lógica es tu red de seguridad contra reintentos accidentales.

El trap del doble-pago (montos que terminan en .03)

El escenario .02 es un timeout "inocente": el banco no hizo nada. El escenario .03 es el malvado, y es el que separa una integración robusta de una que quiebra. Con un monto que termina en .03 — por ejemplo "150003" — el MockProvider simula el peor caso real: el dinero SÍ se movió del lado del banco, pero la respuesta se perdió en el camino, así que Alibanca igual te devuelve unknown.

Piénsalo como una carta que llega a su destino, pero cuyo acuse de recibo se pierde en el correo. Tú, el remitente, no tienes forma de saber si llegó. Si asumes que no llegó y mandas la carta otra vez… el destinatario recibe dos.

!
Aquí es donde una mala integración paga dos veces. Ante el unknown del .03, si tu código "por si acaso" emite un pago nuevo, acabas de dispersar el doble. El .02 y el .03 lucen idénticos desde afuera — ambos son unknown — precisamente para que aprendas a nunca reintentar un unknown, sin importar qué crees que pasó por detrás.

La razón por la que puedes confiar en no reintentar es la conciliación (recon). Alibanca cuadra continuamente el saldo real del banco contra su ledger de doble partida (cada movimiento tiene su contrapartida, como en contabilidad formal). Esa conciliación detecta, entre otras cosas: un débito en el banco sin un payout dueño (un orphan, que escala a excepción P0), un crédito sin fondeo (una devolución), un doble-débito byte-idéntico, o un payout confirmado sin débito real. En el caso del .03, la conciliación ve el débito real, lo casa con tu pago unknown y lo resuelve a completed. Tú no tuviste que hacer nada — y sobre todo, no pagaste dos veces. Lo caza la conciliación antes que tú.

Pruébalo. Emite dos pagos idénticos: uno con amountMinor "150002" y otro con "150003". Ambos te devolverán unknown. Ahora observa: ¿tu código los trata igual? Debería. Si tu integración "reintenta cuando algo queda raro", el .03 te delata como candidato a doble-pago en producción. Mejor descubrirlo aquí, con dinero de mentira.

Un plan de ensayo antes de producción

El sandbox no es solo para "ver si funciona el camino feliz". Es tu campo de tiro para todas las fallas. Antes de pedir tu sk_live_, recorre esta lista con montos mágicos:

1

Camino feliz

Emite un pago con un monto que termine en un céntimo no listado en la tabla de arriba (por ejemplo "150000"). Confirma que llega a completed y que tu sistema lo marca como pagado.

2

Timeout inocente (.02)

Emite con "…02". Verifica que tu código deja el pago "en proceso", no reintenta, y espera la resolución por poll o webhook.

3

Trap del doble-pago (.03)

Emite con "…03". Verifica lo mismo: cero re-emisión. Este es el examen final de tu manejo de unknown.

4

Reintento seguro

Repite un request con la misma idempotency-key y confirma que recibes el mismo pago, no uno nuevo. Así compruebas que tus reintentos de red son seguros.

5

Webhooks

Configura tu endpoint con PUT /v2/webhook, dispara un POST /v2/webhook/test, y verifica la firma del evento contra el JWKS de GET /v2/webhook/keys. Confirma que reaccionas al cambio de estado sin depender solo del poll.

Cuando estos cinco pasos pasen en verde en sandbox, tu integración está lista para producción. Y como el código es el mismo, no vas a descubrir sorpresas al cambiar la sk_test_ por la sk_live_.

Errores comunes y buenas prácticas

Estos son los tropezones que más vemos, y cómo evitarlos:

  • Tratar unknown como failed. El error madre. failed te garantiza que no hubo débito; unknown no te garantiza nada todavía. Reintentar un unknown = doble-pago. Reintentar un failed con key nueva = correcto.
  • Reusar la misma idempotency-key para operaciones distintas. El idempotency-key debe ser único y determinístico por operación lógica. Si reusas un key de un pago viejo para uno nuevo, la API te devuelve el resultado viejo y tu pago nuevo nunca se emite. Un buen key se deriva de datos de tu operación (por ejemplo, tu propio id de orden), no de un timestamp aleatorio.
  • Olvidar que el monto es un string en céntimos. Es "150002", no 150002 (número) ni 1500.02 (float). El dinero nunca se representa como float — los flotantes redondean y en dinero eso es inaceptable. Siempre string, siempre unidades menores.
  • Probar solo el camino feliz. Si nunca ensayaste los seis céntimos de la tabla, no sabes cómo se comporta tu código bajo un rechazo ni bajo un timeout. El sandbox te regala esas fallas — úsalas.
  • Confundir entornos. Verifica environment: "sandbox" con GET /v2/version antes de una sesión de pruebas, y ten mecanismos que impidan usar una sk_test_ en producción o viceversa.
  • Filtrar la llave. La sk_test_ es secreta igual que la sk_live_. Mantenla server-only, fuera del control de versiones, fuera de los logs.
i
El contrato siempre es la fuente de verdad. Los campos, tipos y estados exactos de cada endpoint viven en el contrato OpenAPI público en /v2/openapi.json. Si alguna vez dudas de un nombre de campo o un valor de estado, esa es la referencia canónica.

Próximos pasos

Ya sabes que el sandbox es una réplica sin dinero real del sistema de producción, que los montos mágicos te dejan pedir cada escenario de falla a voluntad, y que la regla de oro — failed se reintenta, unknown se espera — es la que te protege del doble-pago. Con eso puedes construir una integración robusta antes de tocar un solo centavo real.

Para seguir: revisa Autenticación y firma → para dominar el envelope canónico y el nonce con marca de agua; Emitir un pago → para el detalle de POST /v2/payments y sus campos; Webhooks → para recibir los cambios de estado firmados; y Conciliación → para entender cómo Alibanca cuadra el saldo y resuelve cada unknown por ti.