Changelog
Acá listamos cambios que afectan a integradores: endpoints nuevos, campos agregados/cambiados, deprecaciones. Mejoras del dashboard o internas no entran.
2026-10
- Botón “Volver al comercio” en las pantallas finales del checkout. Con el
checkout_urlabierto como página completa y unreturn_urlque es un deep link (fixapp://...), Chrome en Android no abre tu app desde la redirección automática: muestra un cartel que se va solo a los segundos, y el comprador quedaba en la página sin forma de volver. Ahora las pantallas finales (aprobado, rechazado, cancelado, vencido; de pago y de catastro) muestran un botón que lleva al mismoreturn_urlcon el mismostatus. La redirección automática sigue igual. Tu app puede recibir el mismo deep link dos veces: procesalo de forma idempotente. Ver integración en apps móviles. - La página del checkout se entera antes de una cancelación. Si cancelás un pago con el formulario abierto, la página lo saca en hasta 5 segundos (antes, 15). Y si el comprador abre el
checkout_urlde un pago ya cancelado o vencido, ya no ve el formulario: antes lo veía, y podía pagar, hasta la primera consulta. Ver Cancelar. - Cancelar un pago que se aprobaba en el mismo instante ya no lo deja cancelado. Si
POST /v1/payments/{id}/cancelllegaba justo cuando el proveedor aprobaba el pago, podía quedar cancelado con la plata cobrada, y te llegabanpayment.confirmedy despuéspayment.cancelled. Ahora el cancel responde422y el pago queda aprobado. return_urlycancel_urlno aceptan esquemas que ejecutan código.javascript:,data:,vbscript:,file:,blob:yabout:responden422enPOST /v1/payments,POST /v1/cardsyPOST /v1/cards/{id}/charge. Los deep links (fixapp://) y las URLs http/https siguen igual.- Los pagos de Dinelco ya no vencen antes de su plazo. Vencíamos el pago cuando vencía el token de Dinelco para abrir el formulario (unos 5 minutos en live), en vez de en el plazo que pediste con
expires_in: un pago con 15 minutos te mandabapayment.expireda los 5, y si el comprador pagaba después te llegaba unpayment.confirmedmarcado como recuperado. Ahora el pago vence en su plazo (expires_at).checkout.expires_atsigue siendo hasta cuándo se puede abrir el formulario. Ver dos vencimientos distintos. - La página del
checkout_urlsaca el formulario al vencer o al cancelar el pago. Un formulario de tarjeta ya abierto aceptaba pagos sin límite de tiempo. Ahora, al llegar el plazo del pago, la página saca el formulario y avisa conpostMessageingalca.checkout.expired, o redirige a tureturn_urlconstatus=expiredsi está abierta como página completa. Si cancelás el pago conPOST /v1/payments/{id}/cancel, en hasta 15 segundos pasa lo mismo coningalca.checkout.cancelled. Si escuchás lospostMessageo la vuelta porreturn_url, sumá el casoexpired. Ver Cancelar. - Nuevo campo
card: con qué tarjeta se pagó. En la API y en los webhookspayment.*, con el tipo (creditodebit), la marca y el número enmascarado:{ "type": "debit", "brand": "visa", "masked_number": "445653******1096" }. Viene también en los pagos rechazados, y esnullen transferencias. Dinelco informa los tres datos. Bancard informa solo el tipo, ycreditpuede ser un débito que no procesa Bancard. Es un campo agregado, no cambia nada existente. Ver tarjeta usada. - Nuevo campo
provider_approved_at: cuándo aprobó el proveedor.confirmed_ates cuándo confirmamos nosotros el pago; si un aviso del proveedor se perdió, nos podemos enterar minutos después de que el comprador pagó.provider_approved_ates la hora de la aprobación según el proveedor, para que sepas si el comprador pagó a tiempo aunque te enteres tarde. Dinelco lo informa; con Bancard vienenull.confirmed_atyconfirmed_after_expiryno cambian. Si reservás stock o cupo, mirá qué hacer según cuándo liberás la reserva. - El cobro con tarjeta guardada puede volver
pending.POST /v1/cards/{id}/chargecasi siempre devuelveconfirmedofailed, pero Dinelco a veces tarda más de 30 segundos. Antes, en ese caso respondíamos un error502sinpayment_idy el pago quedaba abierto para siempre, sin webhook. Ahora respondemos201con el pago enpendingy en uno o dos minutos le preguntamos a Dinelco cómo terminó: te llegapayment.confirmedopayment.failed. Si en 10 minutos Dinelco no informa nada, el pago pasa afailed. No reintentes con otrareference: si el primero se aprobó, cobrarías dos veces. Si el cobro seguro no se hizo (no pudimos conectar, o Dinelco rechazó el pedido), seguís recibiendo el error de siempre, y ahora el pago quedafailedcon su motivo en vez depending. Ver si la respuesta vuelvepending. - El plazo por defecto de un pago con tarjeta baja de 60 a 15 minutos. Aplica a
dinelcoybancardcuando no mandásexpires_in, y coincide con lo que dura el formulario de tarjeta. Transferencia sigue sin vencimiento. Si necesitás más tiempo, mandáexpires_in. Antes de vencer un pago con tarjeta le preguntamos al proveedor: si estaba aprobado te llegapayment.confirmed, nopayment.expired. Ver Expiración. rejection_reasonsiempre presente enpayment.failed. Los rechazos de Dinelco con checkout llegaban conrejection_reason: nullaunque Dinelco mandara el motivo (por ejemplo, “El cliente canceló el challenge 3DS”). Ahora viene siempre, con el formato"<código>: <texto del proveedor>", o solo el texto si no hay código. También completamos el motivo de los pagos fallidos anteriores. Ver Eventos.- Un intento rechazado de Dinelco ya no cierra el pago al instante. Dentro del iframe de Dinelco el comprador puede tocar Reintentar pago después de un rechazo. Antes, el rechazo te mandaba
payment.faileden el momento, y si el reintento se aprobaba te llegaba después unpayment.confirmedmarcado como recuperado (recovered_from_status: "failed"), aunque nunca hubiera salido de la ventana. Ahora el pago queda abierto mientras se puede reintentar: si se aprueba, te llega un solopayment.confirmed; si la sesión vence sin otro intento, te llegapayment.failedcon el motivo del rechazo, unos minutos después del intento (cuando vence la sesión de Dinelco). Ver múltiples webhooks. - Cierre automático de pagos sin respuesta. Si un proveedor nunca informa cómo terminó un pago, lo cerramos: los pagos con checkout vencen en su plazo, y un pago sin plazo pasa a
faileda los 3 días, con unrejection_reasonque lo explica. Si después aparece una aprobación, te llegapayment.confirmedconrecovered_from_status. Ver Pagos sin respuesta del proveedor. return_urlcon el iframe embebido. Si embebés elcheckout_urlen un<iframe>y además mandastereturn_url, al terminar redirigíamos adentro del iframe, y tu sitio aparecía cargado dentro del checkout. Ahora, cuando está embebido, el resultado llega siempre porpostMessage, y elreturn_urlse usa solo con el checkout abierto como página completa. Ver guía mobile.- Tarjetas de prueba de Dinelco. Las tarjetas sin desafío 3DS (terminadas en 1005 y 2235) sirven solo para pagos: Dinelco no deja catastrarlas. Para probar tarjetas guardadas usá las que tienen desafío. El código del desafío puede cambiar: si
1234deja de funcionar, pedinos el vigente. Ver Entornos.
2026-09
- Documentado el
status=processingen la vuelta del comprador (2026-09-27). La API lo viene mandando, pero no estaba en ninguna de las dos tablas: la del checkout hospedado lo omitía y la de la guía mobile decía que los valores erancompleted,failedocancelledsolamente. Si armaste tu handler con esas tablas,processingte está cayendo en la rama genérica. Significa que cuando el comprador volvió todavía no teníamos la respuesta del proveedor: puede haberse cobrado, y lo más común es que la confirmación llegue unos segundos después. El pago quedapendingy el resultado llega por webhook (payment.confirmed,payment.failed, opayment.expiredsi nunca hay resultado). No le ofrezcas al comprador pagar de nuevo enseguida: si ya estaba aprobado, lo cobrarías dos veces. Corregido el mismo día: la primera versión de esta entrada decía queprocessingimplicaba que no hubo cobro, y es al revés. - Tarjetas de prueba de Bancard (2026-09-27). Estaban las de Dinelco pero no las de Bancard. Ya están publicadas en Entornos.
2026-08
- Cada intento de webhook se firma de nuevo (2026-08-30). Antes el
X-Ingalca-Timestampse acuñaba una sola vez y se reenviaba igual en todos los reintentos, así que un receptor que aplicara la ventana anti-replay de 300 s que recomendamos rechazaba todos los reintentos — y como un4xxno se reintenta, eso cancelaba la entrega en forma definitiva y silenciosa. Ahora el timestamp y la firma se calculan en cada intento; el body se retransmite byte por byte. No tenés que cambiar nada: un timestamp fresco pasa cualquier ventana. Si habías ensanchado la tuya para compensar, ya podés volver a 300 s. Mismo arreglo para el reenvío manual desde el dashboard, que mandaba timestamp nuevo con la firma vieja y por lo tanto nunca validaba. Ver replay attacks. - Corregidos los tiempos de reintento (2026-08-30). La tabla publicada decía
1 min · 5 min · 30 min · 2 hpero el código nunca ejecutaba el primer escalón: los reintentos salían a los 5 min, 35 min, 2,6 h y 14,6 h. Alineamos el código a la tabla. Los 5 intentos ahora caen en t=0, 1 min, 6 min, 36 min y 2 h 36 min, y el último intento pasa de ~14,6 h a ~2,6 h del evento. Si dimensionaste tu reconciliación contra el comportamiento real anterior, ajustala. Ver reintentos. expires_inenPOST /v1/payments(2026-08-29). Los pagos directos ahora aceptan un plazo, igual que el checkout hospedado: mín60s, máx86400. Vencido, el pago pasa aexpiredy recibíspayment.expired. El default depende del proveedor: 3600 s paradinelcoybancard, y sin vencimiento paratransferencia(el comprador va al banco y sube el comprobante horas después — ponerle un default rompería ese flujo). Unexpires_inexplícito gana siempre. Ver Expiración.- La página hospedada avisa y devuelve al comercio al vencer (2026-08-29). El checkout hospedado muestra un contador con el tiempo restante; al llegar a cero cierra el formulario de tarjeta, se lo explica al comprador y a los 5 segundos lo redirige a tu
cancel_urlcon?status=expired&reference=...&checkout_session_id=.... Antes se quedaba trabado en una página muerta. Si ya manejás la vuelta del comprador, agregá el casoexpired— si no, va a caer en tu rama genérica de cancelación. Ver Expiración. - Cuatro campos nuevos en el payload de webhook (2026-08-29). Aditivos, no rompen nada:
expires_at(el plazo del intento),expired_at(cuándo lo marcamos vencido),confirmed_after_expiryyrecovered_from_status. Los dos últimos identifican una confirmación que llegó fuera de la ventana: el formulario de tarjeta vive en el dominio del proveedor y un pago puede aprobarse justo después del vencimiento. Si manejás stock o cupo, usalos para decidir si honrás el pago o lo revertís. Ver confirmaciones fuera de ventana. - Correcciones de documentación (2026-08-29). La doc de webhooks describía un payload con wrapper
datay clavesevent_type/event_iden el body que nunca existió: el payload real es plano, el tipo viene enevent, y elevent_idsolo en el headerX-Ingalca-Event-Id. También estaba mal documentado queamountes un entero (es un string decimal,"150000.00") y quecurrencyes opcional enPOST /v1/payments(es requerido). El código no cambió — solo la documentación, que ahora refleja lo que la API viene devolviendo. Si escribiste tu handler siguiendo esos ejemplos, revisalo. - Checkout hospedado (2026-08-04).
POST /v1/checkout-sessionscrea una sesión de pago y te devuelve unaurla la que redirigís el browser del comprador. Él elige método (Dinelco, Bancard o transferencia) y paga en una página hospedada por nosotros con tu logo y tus colores; vos no construís UI de pago ni embebés iframes. Al terminar vuelve a tusuccess_url/cancel_url, y la confirmación llega por webhook como siempre. Ver Checkout hospedado. - Bancard disponible como proveedor de pago (2026-08).
POST /v1/paymentsaceptaprovider=bancardpara cobro con tarjeta (pago ocasional): el cliente tipea la tarjeta dentro del iframe de Bancard —embebido en tu UI vía elcheckout_urlque te devolvemos, proxeado por el gateway— y el 3DS lo maneja Bancard. La confirmación llega por callback y te la reenviamos comopayment.confirmed/payment.failed. La reversa (POST /v1/payments/{id}/reverse) funciona el mismo día, antes de la liquidación. Solo PYG. Las tarjetas guardadas con Bancard (catastro + cobro con token) llegan en una fase posterior. Ver Elegir método de pago.
2026-07
return_urlcon deep links + guía de integración móvil (2026-07-16).POST /v1/cardsyPOST /v1/paymentsaceptan ahora un camporeturn_url(opcional en integraciones web, obligatorio en apps móviles). Cuando termina el 3DS el proxy redirige a esa URL con?status=X&card_id=Y(opayment_id). Acepta http/https además de esquemas custom (fixapp://,com.fix.app://) para integrar conSFSafariViewController(iOS) yCustom Tabs(Android) usando deep links — el patrón necesario porque los WebView modernos bloquean cookies third-party y rompen el 3DS. Sinreturn_urlla comunicación sigue siendopostMessagecomo antes. Fix relacionado del mismo commit:POST /v1/cardsahora siempre envía unreturnUrlválido a Dinelco (antes se colabanullcuando el integrador no lo mandaba y Dinelco cancelaba el 3DS con “cancelado por el cliente” aunque el OTP hubiera sido correcto). Ver guía mobile para ejemplos por stack.checkout_urlproxeado por el gateway (2026-07-16).POST /v1/cards(Dinelco) devuelve un nuevo camporegistration.checkout_urlque embebés como<iframe src>— servimos el HTML del checkout desdeapi.pay.ingalca.comy proxeamos al proveedor por atrás. Tu app nunca ve URLs ni tokens del proveedor.POST /v1/paymentscon Dinelco (sincard_id) suma un bloquedata.checkoutcon el mismo shape. Los eventos del iframe llegan comopostMessagenormalizados en tu vocabulario:ingalca.registry.completed/.failed/.cancelledpara catastro,ingalca.checkout.*para pagos. Los campos crudos anteriores (integrity_token,session_id,validate_url) siguen en la respuesta como internos — usácheckout_url. Ver guía de catastro y reference de pagos.- Eliminar tarjetas (borrado lógico) + dedup de catastros (2026-07-15).
DELETE /v1/cards/{id}ahora responde204y hace un borrado lógico (la tarjeta quedadeleted, sale del listado y no es cobrable); emitimoscard.deleted. Además, re-catastrar la misma tarjeta ya no acumula duplicados: la registración anterior del mismocustomer_idcon igual número enmascarado/marca/tipo pasa adeletedautomáticamente. Antes elDELETEdevolvía501. Dinelco no expone unregister remoto — si lo habilita, elDELETElo va a usar sin cambiar el contrato. - Idempotencia por
reference(2026-07-14).POST /v1/cards/{id}/chargeyPOST /v1/paymentsdeduplican porreference: reenviar el mismo request (mismareference, mismo body) devuelve el pago original con200, sin cobrar de nuevo; la mismareferencecon otroamount/card_id/currencydevuelve409 REFERENCE_ALREADY_USED. El replay devuelve el resultado original aunque haya terminadofailed— para reintentar de verdad, usá unareferencenueva. Ver idempotencia.
2026-05
target_originacepta múltiples dominios (2026-05-12). Podés mandartarget_origincomo array enPOST /v1/paymentsyPOST /v1/cardspara listar todos los orígenes (dominios/subdominios) desde donde podés embeber el iframe de Dinelco. La forma string (un solo origen) sigue funcionando. Ver Sobre target_origin.- Notificaciones por email configurables (2026-05-02). Te avisamos por email cuando un webhook agota reintentos o cuando un cliente sube un comprobante de transferencia para revisar. Configurás en Configuración → Webhooks → Notificaciones por email. Throttle: máx 1 email por hora (webhooks fallidos) / 30 min (comprobantes).
- Aprobar/rechazar comprobantes vía API (2026-05). Endpoints
POST /v1/payments/{id}/confirmyPOST /v1/payments/{id}/rejectpara automatizar la aprobación de transferencias si tenés tu propia lógica.
2026-04
- Customers como entidad referencial (2026-04). Endpoints
GET /v1/customersyGET /v1/customers/{id}. Auto-create al crear pagos o tarjetas concustomer.customerId. Webhookpayment.*ahora incluyecustomer_id(external_id) en el payload. Ver guía de customers. - Reversa de pagos Dinelco (2026-04).
POST /v1/payments/{id}/reversepara Dinelco antes del corte de liquidación. Webhookpayment.reversed. Mensaje específico cuando ya pasó la ventana (responseCode: 90019). Ver guía de reversa. - Pago Failed (no Pending) cuando falla el create (2026-04). Antes los pagos con payload inválido quedaban como
pendingfantasma. Ahora se marcanfailedconrejection_reason= mensaje del provider. - Sección
/cardsen el dashboard del tenant (2026-04). Listado read-only con drawer.
Si tenés un caso particular o preguntas sobre algún cambio, escribinos a desarrollo@ingalca.com.