Fundamentos de Integración
Esta guía explica los conceptos básicos que todos los flujos de integración comparten. Independientemente del tipo de flujo que elijas (modal, embebido, con carga manual o automática), estos fundamentos son necesarios.
Después de leer esta guía, consultá Modos de Integración para ver ejemplos completos de cada flujo.
URLs de los ambientes
| Ambiente | Iframe (integradores) | API (sesiones) |
|---|---|---|
| PREPROD | https://iframe-preprod.lakautac.com.ar | https://web-preprod.lakautac.com.ar |
| PROD | https://iframe.lakautac.com.ar/embed/ | https://www.lakautac.com.ar |
Las URLs de producción serán diferentes a las de preproducción. Se comunicarán antes del lanzamiento. Asegurate de que tu integración use variables de configuración para cambiarlas fácilmente.
1. Obtener un Session Token
Antes de cargar el iframe, tu backend debe solicitar un token de sesión efímero. Esto evita exponer tu API Key en el frontend.
Tu Backend API Lakaut
│ │
│── POST /api/integration/session/new?id=TU_INTEGRATOR_ID
│ Header: X-Api-Key: TU_API_KEY │
│ │
│<── { tokenSession: "abc-123..." } ─│
│ │
// En tu BACKEND (Node.js, Java, Python, etc.)
// ⚠️ NUNCA hagas esto desde el frontend
// PREPROD: https://web-preprod.lakautac.com.ar
// PROD: https://www.lakautac.com.ar
const LAKAUT_WEB_URL = process.env.LAKAUT_WEB_URL;
async function crearSesionFirma() {
const response = await fetch(
`${LAKAUT_WEB_URL}/api/integration/session/new?id=TU_INTEGRATOR_ID`,
{
method: 'POST',
headers: { 'X-Api-Key': 'TU_API_KEY' } // ← solo en el backend
}
);
const data = await response.json();
return data.tokenSession; // Token efímero de 30 minutos
}
Tu frontend solicita el token a tu propio backend, nunca directamente a Lakaut:
// En tu FRONTEND
const sessionToken = await fetch('/api/firma/session', { method: 'POST' })
.then(r => r.json())
.then(d => d.sessionToken);
Nunca expongas tu API Key en el frontend. La API Key debe vivir exclusivamente en tu backend. El frontend solo maneja el sessionToken efímero.
2. Incluir el iframe
Una vez obtenido el sessionToken, embebé el iframe:
<!--
PREPROD: https://iframe-preprod.lakautac.com.ar/embed/
PROD: https://iframe.lakautac.com.ar/embed
-->
<iframe
id="lakaut-firma"
src="https://iframe-preprod.lakautac.com.ar/embed/?sessionToken=TU_SESSION_TOKEN"
style="width: 100%; height: 600px; border: none;"
allow="clipboard-read; clipboard-write; camera *; microphone"
></iframe>
Parámetros del iframe
| Parámetro | Descripción |
|---|---|
id | Identificador para acceder al iframe desde JavaScript |
src | URL del iframe. Incluye el sessionToken obtenido de tu backend |
allow | Permisos necesarios para el funcionamiento del clipboard y cámara (biometría) |
Mínimo 400px de ancho y 500px de alto para una experiencia óptima.
3. Inicializar la comunicación
El iframe envía un mensaje lakaut.ready cuando está listo. Podés escucharlo o usar iframe.onload:
// Función helper para generar UUIDs con fallback
function uuidv4() {
if (crypto?.randomUUID) {
return crypto.randomUUID();
}
// Fallback RFC4122 v4
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = crypto.getRandomValues(new Uint8Array(1))[0] & 15;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
// PREPROD: https://iframe-preprod.lakautac.com.ar
// PROD: https://www.lakautac.com.ar
const IFRAME_ORIGIN = 'https://iframe-preprod.lakautac.com.ar';
const iframe = document.getElementById('lakaut-firma');
// Opción 1: Escuchar lakaut.ready (recomendado)
window.addEventListener('message', (event) => {
if (event.origin !== IFRAME_ORIGIN) return;
if (event.data.type === 'lakaut.ready') {
enviarInit();
}
});
// Opción 2: Usar onload (alternativa)
iframe.onload = () => {
enviarInit();
};
function enviarInit() {
iframe.contentWindow.postMessage({
type: 'lakaut.init',
payload: {
nonce: uuidv4(),
idemKey: uuidv4(),
sessionToken: 'TU_SESSION_TOKEN', // Obtenido de tu backend
userData: {
dni: '12345678',
cuil: '20-12345678-9',
email: 'usuario@ejemplo.com',
gender: 'M', // 'M', 'F' o 'X'
phone: '1122334455',
name: 'Juan Pérez',
address: 'Av. Corrientes 1234, CABA'
}
}
}, IFRAME_ORIGIN);
}
Campos del mensaje de inicialización
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
nonce | string | ✅ | Identificador único para esta sesión. Usar crypto.randomUUID() |
idemKey | string | ✅ | Clave de idempotencia para evitar firmas duplicadas |
sessionToken | string | ✅ | Token de sesión efímero obtenido desde tu backend |
userData.dni | string | ⚪ | DNI del firmante (8 dígitos). Alternativa a CUIL |
userData.cuil | string | ⚪ | CUIL del firmante en formato XX-XXXXXXXX-X |
userData.email | string | ✅ | Email del firmante |
userData.gender | string | ✅ | Género: 'M' (masculino), 'F' (femenino) o 'X' (otro) |
userData.phone | string | ✅ | Número de teléfono del firmante (10 dígitos) |
userData.name | string | ⚪ | Nombre completo del firmante |
userData.address | string | ⚪ | Domicilio del firmante |
integratorIdSi venías usando integratorId y apiKey en el payload de lakaut.init, reemplazalos por sessionToken. El iframe resuelve internamente el integrador y la API Key a partir del token de sesión.
4. Escuchar eventos del iframe
Configurá un listener para recibir los eventos:
window.addEventListener('message', (event) => {
// IMPORTANTE: Validar el origen del mensaje
// PREPROD: https://iframe-preprod.lakautac.com.ar
// PROD: https://iframe.lakautac.com.ar/embed/
if (event.origin !== IFRAME_ORIGIN) return;
const { type, payload } = event.data;
switch (type) {
case 'lakaut.ready':
// Iframe cargado y listo para recibir init
console.log('Iframe listo');
break;
case 'lakaut.handshake.ack':
// Conexión establecida exitosamente
console.log('Handshake completado ✅');
break;
case 'lakaut.signature.completed':
// Documento firmado exitosamente
handleSignatureCompleted(payload);
break;
case 'lakaut.error':
// Error durante el proceso
handleError(event.data);
break;
}
});
Eventos principales
| Evento | Descripción | Cuándo se dispara |
|---|---|---|
lakaut.ready | Iframe listo para recibir configuración | Al cargar el iframe |
lakaut.handshake.ack | Confirmación de conexión | Después de enviar lakaut.init |
lakaut.signature.completed | Firma completada | Cuando el usuario firma exitosamente |
lakaut.error | Error en el proceso | Cuando ocurre algún error |
Siempre validá que event.origin coincida con la URL del iframe para evitar ataques de seguridad.
5. Manejar el documento firmado
Cuando se completa la firma, recibís el evento lakaut.signature.completed:
function handleSignatureCompleted(payload) {
const { signedDocId, document, delivery } = payload;
// El PDF firmado viene en Base64
if (delivery.mode === 'binary') {
const blob = base64ToBlob(delivery.fileBase64, document.mime);
// Descargar el archivo
downloadBlob(blob, `documento-firmado-${signedDocId}.pdf`);
}
// Guardar referencia en tu sistema
guardarEnBaseDeDatos({
id: signedDocId,
fileName: document.fileName,
sha256: document.sha256,
size: document.size,
fechaFirma: new Date()
});
}
signedDocId y document.fileNamedocument.fileNamepuede venir en el payload como el nombre del archivo firmado.signedDocIdrepresenta el identificador del documento firmado. En la práctica, según el flujo interno, puede venir como un ID real o como un identificador basado en nombre/handle. Por eso, si querés mostrar un nombre de archivo al usuario, usádocument.fileNamecuando esté disponible.
Estructura del payload
| Campo | Descripción |
|---|---|
eventId | ID único del evento de firma |
idemKey | Clave de idempotencia (si se envió en init) |
signedDocId | ID único del documento firmado |
document.fileName | Nombre del archivo firmado |
document.sha256 | Hash SHA-256 del documento |
document.size | Tamaño del documento en bytes |
document.mime | Tipo MIME (siempre application/pdf) |
delivery.mode | Modo de entrega: 'binary' o 'url' |
delivery.fileBase64 | Contenido del PDF firmado en Base64 (si mode='binary') |
delivery.url | URL de descarga (si mode='url') |
delivery)El iframe puede entregar el documento firmado de 2 formas:
delivery.mode = 'binary': el PDF firmado viene endelivery.fileBase64(Base64 sin prefijo).delivery.mode = 'url': el PDF firmado se descarga desdedelivery.url.
Tu integración debe contemplar ambos casos (por ejemplo, priorizar binary si viene y usar url como fallback).
Funciones auxiliares
// Convertir Base64 a Blob
function base64ToBlob(base64, mimeType) {
const byteCharacters = atob(base64);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
return new Blob([byteArray], { type: mimeType });
}
// Descargar Blob
function downloadBlob(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
6. Cargar documento (opcional)
Tenés dos opciones para cargar el documento a firmar:
Opción A: En el init (recomendado)
Incluí el documento directamente en el mensaje lakaut.init:
iframe.contentWindow.postMessage({
type: 'lakaut.init',
payload: {
// ... otros campos
autoLoadFile: {
fileName: 'contrato-servicio.pdf',
mime: 'application/pdf',
base64: tuDocumentoEnBase64 // PDF en Base64 sin prefijo
}
}
}, IFRAME_ORIGIN);
Opción B: Mensaje separado
Enviá el documento después de recibir lakaut.handshake.ack:
window.addEventListener('message', (event) => {
if (event.origin !== IFRAME_ORIGIN) return;
if (event.data.type === 'lakaut.handshake.ack') {
// Ahora podés cargar el documento
iframe.contentWindow.postMessage({
type: 'lakaut.load.file',
payload: {
fileName: 'contrato-servicio.pdf',
mime: 'application/pdf',
base64: tuDocumentoEnBase64 // PDF en Base64 sin prefijo
}
}, IFRAME_ORIGIN);
}
});
El iframe soporta dos mecanismos reales de carga, ambos implementados:
autoLoadFileenlakaut.init: el iframe precarga el documento apenas recibe el init (si vieneautoLoadFile.base64).lakaut.load.file: permite enviar/cambiar el documento luego del init.
Recomendación: si vas a usar lakaut.load.file, envialo después de recibir lakaut.handshake.ack para mantener el orden del protocolo y simplificar el manejo en tu app.
Flujo de comunicación
Tu Backend Tu Frontend Iframe Lakaut
│ │ │
│ │ │
│<── POST /api/firma/session ──────│ │
│── { sessionToken } ────────────>│ │
│ │ │
│ │ [Iframe carga con token] │
│ │ │
│ │<─── lakaut.ready ──────────────│
│ │ │
│ │──── lakaut.init ──────────────>│
│ │ (sessionToken + userData) │
│ │ │
│ │<─── lakaut.handshake.ack ──────│
│ │ │
│ │──── lakaut.load.file (opc.) ─>│
│ │ │
│ │ [Usuario firma] │
│ │ │
│ │<─── lakaut.signature.completed │
│ │ │
Errores comunes
| Error | Causa | Solución |
|---|---|---|
| El iframe no carga | Dominio no autorizado | Contactá a Lakaut para registrar tu dominio |
No recibo lakaut.ready | Iframe bloqueado por CSP | Verificá Content-Security-Policy |
| No recibo el handshake | Origen incorrecto en postMessage | Verificá que estás usando la URL correcta del ambiente |
| Error 401 | Session token expirado | Solicitá un nuevo token desde tu backend |
| El documento no se carga | Enviaste lakaut.load.file antes del handshake | Esperá el evento lakaut.handshake.ack |
| Base64 inválido | Incluiste el prefijo data:... | Enviá solo el contenido base64 sin prefijo |
SESSION_TOKEN_EXPIRED | El token de sesión expiró (30 min) | Tu backend debe generar uno nuevo |
Próximos pasos
Ahora que conocés los fundamentos, elegí el flujo que mejor se adapte a tu caso:
- Modos de Integración - Ejemplos completos de los 4 flujos disponibles
- Referencia de mensajes - Documentación completa de todos los eventos
- Seguridad - Mejores prácticas de seguridad