Webhooks
Los webhooks son la forma en que Alibanca te avisa —en el instante en que ocurre— que el estado de un pago cambió, sin que tengas que preguntar una y otra vez. En vez de que tú llames a la API para revisar (a eso se le dice poll), Alibanca llama a un endpoint que vive en tu servidor apenas hay novedad. En esta guía aprenderás qué son y por qué conviene usarlos junto al poll, cómo configurar y probar tu endpoint, qué eventos recibes, y —lo más importante para tu seguridad— cómo verificar que cada webhook viene de verdad de Alibanca usando firma RSA asimétrica y el JWKS público, sin compartir ningún secreto.
¿Qué es un webhook?
Un webhook es una URL tuya —un endpoint HTTP que tú expones en tu servidor— a la que Alibanca le envía una petición POST cada vez que pasa algo que te interesa. La palabra técnica para eso es callback: le das a Alibanca un número al cual llamarte, y Alibanca te llama cuando hay novedades.
La analogía más simple es la del delivery. Cuando pides comida, tienes dos opciones. Una: llamar cada cinco minutos al restaurante para preguntar "¿ya salió mi pedido?" —eso es poll, tú preguntas activamente. La otra: dejar tu número y que el repartidor te escriba "ya estoy en la puerta" —eso es un webhook, ellos te avisan a ti. Con webhooks te enteras en el momento exacto, sin gastar llamadas de más ni quedarte esperando.
POST con cuerpo JSON.Webhooks y poll: por qué usar los dos
Alibanca te da dos maneras de conocer el estado de un pago, y no son rivales: son un cinturón y unos tirantes. Te conviene usar ambos.
El poll es cuando tú consultas GET /v2/payments/:id → para leer el estado actual de un pago. Es 100% confiable porque el estado siempre está ahí, listo para ser leído; su desventaja es que gastas peticiones y hay latencia entre que algo cambia y tu próxima consulta.
El webhook es la notificación instantánea: en el momento en que el estado cambia, Alibanca te empuja el aviso. Es rápido y eficiente, pero al viajar por internet un webhook puede perderse, llegar tarde o llegar duplicado —eso es normal en cualquier sistema distribuido, no es una falla de Alibanca.
La buena práctica es combinarlos: usa el webhook como disparador para reaccionar al instante, y usa el poll como fuente de verdad para confirmar y para cerrar los casos que el webhook no te alcanzó a entregar. Nunca dependas solo del webhook; el estado que devuelve GET /v2/payments/:id siempre manda.
GET al pago. Así, aunque llegue un webhook viejo o duplicado, tu decisión siempre se basa en el estado real y actual.Los cuatro endpoints de webhook
Toda la administración de tus webhooks vive en cuatro endpoints. Los primeros tres requieren autenticación con tu llave firmada; el de las llaves públicas es lo que usarás para verificar.
Configura tu endpoint
Configurar un webhook es decirle a Alibanca a qué URL tuya debe enviar los eventos. Se hace con PUT (no POST) porque estás fijando tu configuración: si la llamas de nuevo con otra URL, reemplaza la anterior; no crea webhooks nuevos.
| Campo | Tipo | Descripción |
|---|---|---|
| urlreq | string | La URL pública, con HTTPS, de tu endpoint receptor. Es donde Alibanca hará el POST de cada evento. Debe estar accesible desde internet y responder rápido. |
# Firma la petición igual que cualquier otra: x-api-key + x-signature + idempotency-key curl -X PUT https://alibanca-api-production.up.railway.app/v2/webhook \ -H "x-api-key: sk_test_tu_llave" \ -H "x-signature: <firma-rsa-sha256-del-envelope>" \ -H "idempotency-key: set-webhook-2026-07-22-001" \ -H "Content-Type: application/json" \ -d '{"url":"https://tuapp.com/hooks/alibanca"}'
{ "status": "success", "traceId": "trc_9f2a...", "payload": { "url": "https://tuapp.com/hooks/alibanca" } }
Para revisar en cualquier momento qué tienes configurado, usa GET /v2/webhook →. La misma información también aparece en tu identidad: GET /v2/me → devuelve un objeto webhook con dos campos, url (la URL vigente) y pendingChange (un cambio de URL que quedó registrado y aún no toma efecto). Si ves algo en pendingChange, es tu señal de que hay una modificación en tránsito.
Prueba tu configuración
Antes de esperar un pago real, comprueba que tu endpoint recibe y verifica bien. Para eso está POST /v2/webhook/test: le pides a Alibanca que te envíe un evento de mentira a tu URL configurada, con la misma forma y la misma firma que uno real. Es la manera de validar tu integración de punta a punta sin mover un solo pago.
PUT /v2/webhook y luego llama a POST /v2/webhook/test. Vas a recibir en tu servidor un evento firmado idéntico en estructura a los de producción. Úsalo para verificar la firma y tu manejo de idempotencia antes de emitir tu primer pago.curl -X POST https://alibanca-api-production.up.railway.app/v2/webhook/test \ -H "x-api-key: sk_test_tu_llave" \ -H "x-signature: <firma>" \ -H "idempotency-key: webhook-test-001"
Segundos después, tu endpoint debería recibir un POST con un cuerpo JSON firmado. Si no llega, lo primero que revisas es: ¿tu URL es pública y con HTTPS? ¿Tu servidor respondió un 2xx? ¿Un firewall bloqueó la petición entrante?
Los eventos: cambios de estado de un pago
Hoy los webhooks de Alibanca notifican cambios de estado de un pago. Cuando un pago que emitiste con POST /v2/payments → avanza en su ciclo de vida, te llega un evento con el nuevo estado. Los estados posibles son tres:
completed significa que el dinero se movió y el banco lo confirmó. failed significa que el pago no ocurrió y —esta es la regla de oro— garantiza que no hubo débito: es seguro reintentar con una idempotency-key nueva. unknown es un estado de primera clase, no un error: el banco no respondió a tiempo (timeout o caída), así que Alibanca todavía no sabe el resultado y lo está reconciliando contra el saldo real.
unknown. Si te llega un webhook con estado unknown, no emitas otro pago: podrías estar duplicando plata que sí se movió. Un unknown se resuelve solo, por conciliación de saldo, y terminará en completed o en failed. Reintentar con idempotency-key nueva es seguro únicamente tras un failed. Detalle en Estados de un pago →.Anatomía de un webhook firmado
Cada webhook llega como un POST a tu URL. En el cuerpo viene el evento en JSON; en las cabeceras viene la firma y una referencia a la llave con la que se firmó. Un ejemplo de lo que recibe tu servidor:
// Cabeceras x-signature: "a91c4d...<firma RSA-SHA256 en base64>" x-webhook-key-id: "wk_2026_07" // el kid: cuál llave del JWKS lo firmó Content-Type: "application/json" // Cuerpo (raw body: exactamente estos bytes son los que se firmaron) { "type": "payment.state.changed", "apiVersion": "v2", "traceId": "trc_71b0e2...", "payload": { "id": "pay_3Kfd8...", "state": "completed", "subStatus": "settled", "amountMinor": "150000", "currency": "VES" } }
raw body), no sobre el JSON reinterpretado. Por eso, al recibir un webhook, guarda el cuerpo tal cual llegó antes de parsearlo. Si tu framework parsea y luego re-serializa el JSON, puede cambiar espacios u orden de claves y la firma dejará de cuadrar aunque el contenido sea el mismo.La firma asimétrica y el JWKS
Aquí está lo que hace a los webhooks de Alibanca seguros de verdad. Cualquiera en internet podría intentar hacer un POST a tu URL fingiendo ser Alibanca. La firma es lo que te deja distinguir un webhook auténtico de uno falso.
Alibanca firma cada webhook con criptografía RSA asimétrica (RSA-SHA256). "Asimétrica" quiere decir que hay dos llaves distintas: una privada, que solo Alibanca conoce y usa para firmar, y una pública, que cualquiera puede usar para verificar esa firma pero que no sirve para falsificarla. Es como una firma de puño y letra: tú puedes reconocer que es la mía, pero no puedes reproducirla.
La ventaja frente al modelo clásico de "secreto compartido" (donde tú y el proveedor guardan la misma contraseña) es enorme: no hay ningún secreto que compartir. Tú nunca posees material sensible de Alibanca; solo su llave pública. Aunque tu servidor fuera comprometido, el atacante no obtendría nada con qué falsificar webhooks. La llave privada nunca sale del lado de Alibanca.
Las llaves públicas se publican en un JWKS (JSON Web Key Set, un conjunto de llaves en formato JSON estándar) que obtienes en GET /v2/webhook/keys →. Es un endpoint que devuelve una o más llaves públicas, cada una identificada por su kid (key id). Cuando Alibanca rota sus llaves —cambiarlas de vez en cuando es una buena práctica de seguridad— el JWKS puede contener varias a la vez, y cada webhook te dice con cuál kid fue firmado.
{ "keys": [ { "kty": "RSA", // tipo de llave: RSA "use": "sig", // uso: firma (signature) "alg": "RS256", // algoritmo: RSA-SHA256 "kid": "wk_2026_07", // identificador: casa con x-webhook-key-id "n": "0vx7agoebGcQSuu...<módulo en base64url>", "e": "AQAB" // exponente público } ] }
kid no reconozcas, vuelve a pedir GET /v2/webhook/keys —probablemente Alibanca rotó su llave y necesitas la nueva. También lo encuentras enlazado desde GET /v2/version → en links.jwks.Verifica la firma, paso a paso
Verificar es obligatorio: un webhook cuya firma no cuadra debe descartarse, sin importar lo convincente que se vea. Este es el procedimiento completo, en orden.
Captura el cuerpo crudo
Antes de parsear nada, guarda el raw body tal cual llegó, byte por byte. Ese es el material exacto sobre el que se calculó la firma. Si dejas que tu framework lo parsee y re-serialice primero, la verificación fallará por diferencias invisibles de formato.
Lee la firma y el kid
Toma la firma de la cabecera x-signature y el identificador de llave de x-webhook-key-id. El kid te dice cuál de las llaves del JWKS debes usar.
Elige la llave pública correcta
Busca en el JWKS (que tienes cacheado) la llave cuyo kid coincide. Si no está, vuelve a pedir GET /v2/webhook/keys y busca de nuevo. Si aun así no aparece, rechaza el webhook.
Verifica RSA-SHA256
Con la llave pública, verifica que la firma corresponde al cuerpo crudo usando RS256 (RSA-SHA256). Si la verificación es válida, el webhook es auténtico. Si no, es falso o fue alterado en el camino: descártalo.
Recién ahí, procesa
Solo después de verificar, parsea el JSON y actúa. Y recuerda la regla de oro del combo: usa el evento como disparador y confirma el estado real con un GET al pago antes de mover plata en tu sistema.
import crypto from "node:crypto"; // req.rawBody = el cuerpo crudo capturado ANTES de parsear function verificarWebhook(req, jwks) { const firma = req.headers["x-signature"]; const kid = req.headers["x-webhook-key-id"]; // 1. ubica la llave pública por su kid const jwk = jwks.keys.find(k => k.kid === kid); if (!jwk) throw new Error("kid desconocido, refresca el JWKS"); const pub = crypto.createPublicKey({ key: jwk, format: "jwk" }); // 2. verifica RSA-SHA256 sobre el cuerpo CRUDO const ok = crypto.verify( "RSA-SHA256", Buffer.from(req.rawBody), pub, Buffer.from(firma, "base64") ); if (!ok) throw new Error("firma invalida: descartar"); return JSON.parse(req.rawBody); // recien aqui es seguro parsear }
Reintentos y entrega
La red no es perfecta, así que Alibanca no asume que un solo intento basta. Si tu endpoint no responde con un código 2xx —porque estaba caído, lento o devolvió un error— Alibanca reintenta la entrega. Esto es lo que garantiza que, tarde o temprano, el evento te llegue.
La consecuencia práctica es que tu endpoint debe estar preparado para recibir el mismo evento más de una vez. Un reintento no significa que el pago cambió dos veces; significa que la primera entrega no se confirmó. Por eso el diseño de tu receptor tiene que ser tolerante a duplicados —lo vemos en las buenas prácticas abajo.
GET /v2/payments/:id; el subStatus y el timing definitivo salen de ahí, no del orden de tus webhooks.Buenas prácticas del receptor
Un buen endpoint de webhooks se construye alrededor de tres principios. Seguirlos te ahorra los bugs más comunes de las integraciones.
Responde 2xx rápido, procesa después
Tu único trabajo inmediato al recibir un webhook es verificar la firma y responder 200 lo antes posible. No hagas la lógica pesada —consultas a tu base de datos, llamar a otros servicios, mover plata— dentro de la misma respuesta HTTP. Si tardas demasiado, Alibanca lo interpretará como fallo y reintentará, generándote entregas duplicadas. El patrón correcto: verifica, encola el evento en tu cola interna, responde 2xx, y procesa el trabajo real en segundo plano.
Sé idempotente del lado del receptor
Como los webhooks pueden repetirse, tu procesamiento debe ser idempotente: recibir el mismo evento dos veces produce el mismo resultado que recibirlo una. La forma clásica de lograrlo es llevar un registro de los eventos ya procesados —por su traceId o por el id del pago más el state— y descartar el que ya viste. Así, un reintento no te dispara dos correos, dos asientos contables ni dos acciones sobre el mismo pago.
idempotency-key de los pagos. Al emitir pagos usas una idempotency-key → para que un reintento no cree un pago nuevo. En el receptor de webhooks aplicas la misma idea en tu propio lado: deduplicar por identidad del evento. Reintentos seguros de punta a punta.Verifica siempre, sin excepciones
Nunca proceses un webhook sin haber validado su firma contra el JWKS. Un endpoint que confía en cualquier POST que le llega es una puerta abierta a que un tercero te inyecte eventos falsos —por ejemplo, un falso completed para que liberes plata que nunca se movió. La firma es tu defensa, y no cuesta más que unas líneas.
Confirma con el poll antes de actuar sobre dinero
Repetimos esto porque es la práctica que más problemas evita: trata el webhook como una señal para mirar, no como la verdad final. Antes de cualquier movimiento de dinero en tu sistema, confirma el estado con GET /v2/payments/:id. Y respeta las reglas de estado: sobre un failed puedes reintentar con idempotency-key nueva; sobre un unknown jamás emites un pago nuevo, se resuelve por conciliación.
Errores comunes
Estos son los tropiezos que vemos una y otra vez en integraciones nuevas. Revísalos como checklist:
| Síntoma | Causa | Solución |
|---|---|---|
| La firma nunca cuadra | raw body | Estás verificando sobre el JSON re-serializado por tu framework, no sobre el cuerpo crudo. Captura y verifica los bytes exactos que llegaron. |
kid desconocido | rotación | Alibanca rotó su llave y tu JWKS cacheado quedó viejo. Vuelve a pedir GET /v2/webhook/keys y reintenta la verificación. |
| Acciones duplicadas | idempotencia | Tu receptor no deduplica. Registra los eventos procesados y descarta los repetidos por su identidad. |
| Alibanca reintenta sin parar | respuesta lenta | Tardas mucho en responder o devuelves error. Responde 2xx apenas verificas y mueve el trabajo pesado a segundo plano. |
| Liberaste plata de más | confianza ciega | Actuaste sobre un webhook viejo o duplicado sin confirmar. Confirma siempre con GET /v2/payments/:id antes de mover dinero. |
| No llega ningún webhook | configuración | URL no pública, sin HTTPS, o bloqueada por firewall. Prueba con POST /v2/webhook/test y revisa GET /v2/webhook. |
Con eso tienes el ciclo completo: configuras tu endpoint, lo pruebas, recibes eventos firmados, los verificas contra el JWKS público sin ningún secreto compartido, y actúas sobre ellos de forma idempotente y confirmando siempre con el poll. Para el detalle del ciclo de vida de un pago, sigue con Estados de un pago →; para firmar tus propias peticiones salientes, revisa Autenticación y firma →.