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 — un pago exitoso, 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.
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:
| Prefijo | Entorno | Qué hace |
|---|---|---|
| sk_test_ | Sandbox | Apunta al MockProvider. No mueve dinero real. Úsala para todo tu desarrollo y tus pruebas. |
| sk_live_ | Producción | Apunta 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.)
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.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.
curl https://alibanca-api-production.up.railway.app/v2/version
{ "version": "v0.13.0", "apiVersion": "v2", "environment": "sandbox", "rails": ["PM", "CCE"], "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).
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 devuelve | Qué escenario ensayas |
|---|---|---|
| .02 | unknown | El 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. |
| .03 | unknown | El 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. |
| cualquier otro céntimo | completed | Camino feliz. El pago se confirma de inmediato. Úsalo para el flujo normal. |
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.
La regla de oro: unknown no es failed
Un pago en Alibanca vive en uno de tres estados, que ves en el campo state (acompañado de un subStatus con más detalle):
La diferencia entre unknown y failed es la lección más cara del negocio de mover dinero, así que grábatela:
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.
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.
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.
failed garantiza cero débito → seguro reintentar con idempotency-key nueva. unknown = indeterminado → jamás se reintenta ni se crea un pago nuevo encima; se resuelve solo por conciliación de saldo. Confundir estos dos estados es la forma número uno de pagar 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.
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 "idempotency-key: ensayo-unknown-2026-07-22-001" \ -H "Content-Type: application/json" \ -d '{"amountMinor":"150002","currency":"VES","rail":"PM","beneficiary":"04141234567"}'
{ "status": "success", "traceId": "trc_9f2c…", "payload": { "id": "pay_01H…", "amountMinor": "150002", "currency": "VES", "rail": "PM", "state": "unknown", // <- el .02 disparó el timeout "subStatus": "pending_reconciliation" } }
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:
curl https://alibanca-api-production.up.railway.app/v2/payments/pay_01H… \ -H "x-api-key: sk_test_tu_llave" \ -H "x-signature: <firma del GET>"
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 unPOST /v2/paymentsnuevo. Eso es exactamente lo que produce un doble-pago. Tu código debe tratarunknowncomo "en curso", no como "falló". - Que tu poll sea paciente. Consulta el estado con
GET /v2/payments/:iden 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 ununknown. - 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.
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ú.
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:
Camino feliz
Emite un pago con un monto que termine en cualquier céntimo distinto de .02/.03 (por ejemplo "150000"). Confirma que llega a completed y que tu sistema lo marca como pagado.
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.
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.
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.
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
unknowncomofailed. El error madre.failedte garantiza que no hubo débito;unknownno te garantiza nada todavía. Reintentar ununknown= doble-pago. Reintentar unfailedcon key nueva = correcto. - Reusar la misma idempotency-key para operaciones distintas. El
idempotency-keydebe 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
stringen céntimos. Es"150002", no150002(número) ni1500.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
.02y.03, no sabes cómo se comporta tu código bajo timeout. El sandbox te regala esas fallas — úsalas. - Confundir entornos. Verifica
environment: "sandbox"conGET /v2/versionantes de una sesión de pruebas, y ten mecanismos que impidan usar unask_test_en producción o viceversa. - Filtrar la llave. La
sk_test_es secreta igual que lask_live_. Mantenla server-only, fuera del control de versiones, fuera de los logs.
/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.