Quickstart: Integración con iframe
Integrá la firma digital en tu aplicación web siguiendo esta guía rápida.
Al finalizar, tendrás un iframe funcional que permite a tus usuarios firmar documentos digitalmente sin salir de tu aplicación.
Para una guía más detallada, consultá Fundamentos de Integración.
URLs de los ambientes
| Ambiente | iframe | 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 |
Requisitos previos
Antes de comenzar, asegurate de tener:
- ✅ Tu
integratorIdyAPI Key(proporcionados por Lakaut) - ✅ Dominio registrado en la lista de orígenes autorizados
- ✅ Aplicación servida sobre HTTPS
Contactá a Lakaut para obtener tu integratorId, tu API Key y registrar tu dominio.
Tu API Key debe vivir exclusivamente en tu backend. Nunca la expongas en el frontend. El frontend solo maneja el sessionToken efímero.
Paso 1: Obtener un Session Token (backend)
Tu backend solicita un token de sesión efímero a Lakaut. Esto evita exponer tu API Key en el frontend.
// 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;
app.post('/api/firma/session', async (req, res) => {
const response = await fetch(
`${LAKAUT_WEB_URL}/api/integration/session/new?id=TU_INTEGRATOR_ID`,
{
method: 'POST',
headers: { 'X-Api-Key': process.env.LAKAUT_API_KEY }
}
);
const data = await response.json();
res.json({ sessionToken: data.tokenSession });
});
Tu frontend solicita el token a tu propio backend:
// En tu FRONTEND
const sessionToken = await fetch('/api/firma/session', { method: 'POST' })
.then(r => r.json())
.then(d => d.sessionToken);
Paso 2: Agregar el iframe a tu HTML
Una vez obtenido el sessionToken, incluí el iframe en tu página:
<!--
PREPROD: https://iframe-preprod.lakautac.com.ar/embed/
PROD: https://www.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>
Reemplazá TU_SESSION_TOKEN con el token obtenido de tu backend en el Paso 1.
Paso 3: Inicializar la comunicación
El iframe envía el evento lakaut.ready cuando está listo para recibir lakaut.init.
// 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);
});
}
const iframe = document.getElementById('lakaut-firma');
// PREPROD: https://iframe-preprod.lakautac.com.ar
// PROD: https://www.lakautac.com.ar
const IFRAME_ORIGIN = 'https://iframe-preprod.lakautac.com.ar';
function enviarInit() {
iframe.contentWindow.postMessage({
type: 'lakaut.init',
payload: {
nonce: uuidv4(),
idemKey: uuidv4(),
sessionToken: sessionToken, // Obtenido de tu backend en Paso 1
userData: {
// Requeridos para que el flujo pueda operar
dni: '12345678', // requerido si no enviás CUIL
cuil: '20-12345678-9', // requerido si no enviás DNI
email: 'usuario@ejemplo.com',
gender: 'M', // 'M', 'F' o 'X'
phone: '1122334455', // recomendado: 10 dígitos
// Opcionales (se pueden completar vía RENAPER)
name: 'Juan Pérez',
address: 'Av. Corrientes 1234, CABA'
}
}
}, IFRAME_ORIGIN);
}
// Esperar el evento lakaut.ready (recomendado)
window.addEventListener('message', (event) => {
if (event.origin !== IFRAME_ORIGIN) return;
if (event.data.type === 'lakaut.ready') {
enviarInit();
}
});
Nota: lakaut.init es un paso obligatorio. Se envía una vez por sesión para inicializar la comunicación.
Paso 4: Escuchar eventos del iframe
Agregá un listener para:
- Confirmar conexión (
lakaut.handshake.ack) - Recibir el resultado (
lakaut.signature.completed) - Manejar errores (
lakaut.error)
window.addEventListener('message', (event) => {
if (event.origin !== IFRAME_ORIGIN) return;
const { type, payload } = event.data;
switch (type) {
case 'lakaut.handshake.ack':
console.log('✅ Conexión establecida');
// Opcionalmente, cargar documento automáticamente
// cargarDocumento();
break;
case 'lakaut.signature.completed':
console.log('✅ Documento firmado');
manejarDocumentoFirmado(payload);
break;
case 'lakaut.error':
// IMPORTANTE: en lakaut.error, code y message están en el nivel raíz
console.error('❌ Error:', event.data.code, event.data.message);
break;
}
});
Paso 5: Cargar documento (opcional)
Este paso es opcional y aplica a flujos con carga automática.
Tenés dos opciones:
Opción A: Cargar en lakaut.init (recomendado)
function enviarInitConDocumento(pdfBase64) {
iframe.contentWindow.postMessage({
type: 'lakaut.init',
payload: {
nonce: uuidv4(),
idemKey: uuidv4(),
sessionToken: sessionToken, // Obtenido de tu backend
userData: {
dni: '12345678',
email: 'usuario@ejemplo.com',
gender: 'M',
phone: '1122334455'
},
autoLoadFile: {
fileName: 'contrato.pdf',
mime: 'application/pdf',
base64: pdfBase64
}
}
}, IFRAME_ORIGIN);
}
Opción B: Mensaje separado lakaut.load.file
Enviá el documento después de recibir lakaut.handshake.ack:
function cargarDocumento() {
// Tu documento PDF en Base64 (sin el prefijo data:...)
const pdfBase64 = 'JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PC...';
iframe.contentWindow.postMessage({
type: 'lakaut.load.file',
payload: {
fileName: 'contrato.pdf',
mime: 'application/pdf',
base64: pdfBase64
}
}, IFRAME_ORIGIN);
}
Si no enviás autoLoadFile ni lakaut.load.file, el usuario podrá subir el documento manualmente desde el iframe.
Paso 6: Manejar el documento firmado
Cuando el usuario complete la firma, recibís el evento lakaut.signature.completed.
function manejarDocumentoFirmado(payload) {
const { signedDocId, document, delivery } = payload;
// Guardar referencia en tu sistema
console.log('signedDocId:', signedDocId);
console.log('Hash SHA-256:', document?.sha256);
console.log('Tamaño:', document?.size, 'bytes');
if (!delivery) return;
if (delivery.mode === 'binary' && delivery.fileBase64) {
const blob = base64ToBlob(delivery.fileBase64, document.mime);
downloadBlob(blob, document?.fileName || `documento-firmado-${signedDocId}.pdf`);
return;
}
if (delivery.mode === 'url' && delivery.url) {
window.open(delivery.url, '_blank', 'noopener,noreferrer');
return;
}
}
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 });
}
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);
}
signedDocId y document.fileNameEn la práctica, document.fileName puede venir y sirve para nombrar el archivo. signedDocId identifica el documento firmado (puede variar según el flujo interno), por eso para el nombre de archivo conviene priorizar document.fileName cuando esté disponible.
Problemas comunes
El iframe no carga
- Verificá que tu dominio esté autorizado
- Asegurate de usar HTTPS
- Revisá la consola del navegador por errores
No recibo lakaut.ready
- Verificá CSP (
frame-src https://iframe-preprod.lakautac.com.ar) - Verificá que el iframe no esté siendo bloqueado por extensiones / adblock
No recibo el handshake
- Verificá que envíes
lakaut.initdespués de recibirlakaut.ready - Confirmá que el segundo parámetro de
postMessagesea la URL correcta del ambiente - Validá que tu
sessionTokensea válido y no esté expirado
El documento no se carga
- Asegurate de enviar
lakaut.load.filedespués delakaut.handshake.ack - Verificá que el Base64 no incluya el prefijo
data:application/pdf;base64, - Confirmá que el PDF sea válido
Session Token expirado
- El
sessionTokentiene una validez de 30 minutos - Si recibís el error
SESSION_TOKEN_EXPIRED, solicitá un nuevo token desde tu backend