Eventos y Callbacks
Tres mecanismos complementarios para rastrear la firma: callbacks del SDK para tu UI, webhooks para tu backend, y return URLs para flujos simples.
La regla de oro
Callbacks del SDK son para feedback instantáneo al usuario. Webhooks son la fuente de verdad para tu backend. Return URLs son la alternativa simple cuando no necesitas webhooks.
Nunca tomes acciones críticas (liberar un pago, activar un servicio) basándote solo en callbacks del SDK. Siempre confirma via webhook o return URL.
┌──────────────┐ onSuccess() ┌──────────────┐
│ JS SDK │ ──────────────────────→ │ Tu frontend │ → Actualiza UI
│ (iFrame) │ onEvent() │ │ → Analytics
└──────────────┘ └──────────────┘
│
│ (AllSign server-side)
▼
┌──────────────┐ webhook POST ┌──────────────┐
│ AllSign API │ ──────────────────────→ │ Tu backend │ → Acciones críticas
│ │ │ │
└──────────────┘ └──────────────┘
1. Callbacks del SDK (client-side)
Los callbacks se configuran al montar el widget:
const session = AllSign.init({
clientSecret: 'as_sess_xxx_secret_yyy',
onReady: () => { /* widget cargado */ },
onSuccess: (result) => { /* firmado */ },
onExit: (metadata) => { /* sesión terminó */ },
onEvent: (event) => { /* paso a paso */ },
});
session.modal();
onReady
Se dispara cuando el widget terminó de cargar y el firmante puede interactuar.
onReady: () => {
// Oculta tu skeleton/loader
document.getElementById('loader').style.display = 'none';
}
onSuccess(result)
Se dispara cuando el firmante completó su firma exitosamente.
onSuccess: (result) => {
console.log(result.signatureId); // UUID de la firma
console.log(result.documentId); // UUID del documento
console.log(result.signerEmail); // Email del firmante
console.log(result.signedAt); // Timestamp ISO-8601
}
| Campo | Tipo | Descripción |
|---|---|---|
signatureId | string | UUID de la firma creada |
documentId | string | UUID del documento |
signerEmail | string | Email del firmante |
signedAt | string | Timestamp ISO-8601 |
success_url | string | null | URL de redirección configurada al crear la sesión. Tú controlas cuándo y cómo redirigir. |
El widget ya no se cierra solo al firmar. Tras onSuccess, el iframe muestra una vista post-firma: el documento en vivo con las firmas de todos (mientras faltan firmantes) o el PDF de evidencia cuando ya firmaron todos. Se cierra cuando el firmante toca "Cerrar" (ahí se dispara onExit). No destruyas el iframe en onSuccess si quieres que el firmante vea esa vista.
onExit(metadata)
Se dispara siempre que la sesión termina, sin importar la razón. Es tu "catch-all" para cleanup.
Para reason: 'completed', onExit ahora es diferido: se dispara cuando el firmante cierra la vista post-firma (toca "Cerrar"), no en el instante en que firma. Si necesitas el momento exacto de la firma, usa onSuccess.
onExit: (metadata) => {
switch (metadata.reason) {
case 'completed':
showSuccessScreen();
break;
case 'cancelled':
showCancelledMessage();
break;
case 'expired':
refreshAndRetry();
break;
case 'error':
console.error(metadata.error?.code, metadata.error?.message);
break;
}
// metadata.lastStep te dice dónde estaba el firmante cuando salió
analytics.track('signing_exit', {
reason: metadata.reason,
lastStep: metadata.lastStep, // 'otp', 'id_scan', 'signature', etc.
});
}
| Campo | Tipo | Descripción |
|---|---|---|
reason | 'completed' | 'cancelled' | 'expired' | 'error' | Razón de fin de sesión |
lastStep | string | null | Último paso completado antes de salir |
sessionId | string | ID de la Signing Session |
documentId | string | UUID del documento |
error | { code: string, message: string } | undefined | Presente si reason === 'error' |
success_url | string | null | URL de redirección post-firma (si se configuró) |
cancel_url | string | null | URL de redirección post-cancelación (si se configuró) |
lastStep es invaluable para entender dónde abandonan los firmantes: ¿se van en el OTP? ¿En el escaneo de INE? ¿En la firma misma?
Cuando reason === 'error', metadata.error.code te dice exactamente qué pasó y cómo recuperarte — reintentar con el mismo client_secret o crear una sesión nueva. La taxonomía completa de códigos y el patrón de recuperación están en Errores del flujo embebido.
onEvidence(evidence)
Se dispara una vez cuando el PDF de evidencia está disponible — es decir, cuando todos los firmantes ya firmaron y la evidencia terminó de generarse. Útil para que muestres o descargues el documento final desde tu propia UI.
onEvidence: (evidence) => {
// URL presignada de descarga (corta vida). null si no se pudo generar.
if (evidence.downloadUrl) {
window.open(evidence.downloadUrl, '_blank');
}
}
| Campo | Tipo | Descripción |
|---|---|---|
signatureId | string | UUID de la firma del firmante actual |
documentId | string | UUID del documento completado |
downloadUrl | string | null | URL presignada (corta vida) para descargar el PDF de evidencia. null si no se pudo generar (reintenta o usa webhooks) |
onEvidence es para conveniencia de UI. Para acciones críticas de backend (archivar la evidencia, etc.) usa el webhook document.completed, que es la fuente autoritativa.
onEvent(event) — eventos granulares
Se dispara en cada paso del flujo. Ideal para analytics, progress bars y debugging.
onEvent: (event) => {
// Muestra progreso en tu UI
updateProgressBar(event.name);
// Envía a tu analytics
analytics.track(`allsign_${event.name}`, event.metadata);
}
Eventos disponibles (en orden del flujo):
Esta lista es el conjunto completo de event.name tipados por el SDK
(SigningEventName en @allsign/embedded). Es la superficie de contrato:
escribe tu onEvent para tolerar cualquiera de estos nombres.
| Evento | Cuándo se dispara | Metadata (best-effort) |
|---|---|---|
SESSION_STARTED | Widget montado y cargado | {} |
OTP_SENT | Código OTP enviado | p.ej. { channel: 'email' | 'whatsapp' } |
OTP_VERIFIED | Código OTP verificado | {} |
ID_SCAN_STARTED | Inició escaneo de INE/identificación | {} |
ID_SCAN_COMPLETED | Escaneo terminado | p.ej. { documentType: 'ine' | 'passport' } |
IDV_STARTED | Inició la verificación de identidad (IDV) | {} |
IDV_FAILED | La verificación de identidad falló | p.ej. { reason: string } |
SELFIE_CAPTURED | Selfie capturada | {} |
BIOMETRIC_VERIFIED | Face match exitoso | p.ej. { confidence: number } |
VIDEO_FIRMA_STARTED | Inició la grabación de VideoFirma | {} |
VIDEO_FIRMA_COMPLETED | VideoFirma grabada | {} |
NAME_CONFIRMED | Firmante confirmó su nombre | {} |
VARIABLES_FILLED | Variables del formulario completadas | p.ej. { fieldCount: number } |
DOCUMENT_VIEWED | Firmante vio el documento | p.ej. { pageCount: number } |
PIPELINE_STEP_STARTED | Inició un paso del pipeline de verificación | p.ej. { step: string } |
PIPELINE_STEP_COMPLETED | Se completó un paso del pipeline | p.ej. { step: string, phase: string } |
SIGNATURE_DRAWN | Firmante dibujó su firma en el canvas | {} |
FEA_SIGNED | Firma con e.firma SAT (FEA) completada | {} |
SIGNATURE_SUBMITTED | Firma enviada al servidor | {} |
SESSION_COMPLETED | Flujo completo terminado | p.ej. { signatureId: string } |
No todos los eventos se disparan en todas las sesiones — reflejan los pasos
reales del pipeline configurado para ese documento (si no hay IDV, no verás
ID_SCAN_* ni IDV_*; sin VideoFirma, no verás VIDEO_FIRMA_*). Hoy el
widget emite de forma confiable SESSION_STARTED, PIPELINE_STEP_COMPLETED,
SIGNATURE_SUBMITTED y SESSION_COMPLETED; el resto se irá emitiendo conforme
evoluciona el flujo. El SDK es forward-compatible: si en el futuro llega un
event.name que aún no está en esta lista, tu onEvent igual lo recibe (no se
descarta), así que nunca asumas un conjunto cerrado.
El metadata por-evento es best-effort, no un contrato tipado. En el SDK, SigningEvent.metadata es un Record<string, unknown> sin tipado por-evento; los campos de la columna anterior (channel, documentType, confidence, etc.) los emite la signing workspace y pueden variar o no estar presentes. Trata event.name como el contrato estable y los campos de metadata como datos opcionales que debes validar antes de usar.
Ejemplo: Progress bar con onEvent
const steps = {
'SESSION_STARTED': { label: 'Cargando...', progress: 10 },
'OTP_VERIFIED': { label: 'Identidad verificada', progress: 25 },
'ID_SCAN_COMPLETED': { label: 'INE escaneada', progress: 50 },
'BIOMETRIC_VERIFIED': { label: 'Biométricos verificados', progress: 65 },
'DOCUMENT_VIEWED': { label: 'Revisando documento', progress: 80 },
'SIGNATURE_SUBMITTED': { label: 'Firmando...', progress: 95 },
'SESSION_COMPLETED': { label: 'Completado', progress: 100 },
};
const session = AllSign.init({
clientSecret,
onEvent: (event) => {
const step = steps[event.name];
if (step) {
progressBar.style.width = `${step.progress}%`;
statusText.textContent = step.label;
}
},
});
session.inline('#container');
2. Webhooks (server-side)
Los webhooks envían un POST a tu servidor cuando ocurren eventos. Configúralos desde el Dashboard o vía la API de Webhooks.
Eventos relevantes para Embedded Signing
| Evento | Cuándo se dispara |
|---|---|
signer.signed | Un firmante completó su firma |
document.completed | Todos los firmantes firmaron — PDF final disponible |
document.expired | El documento expiró sin completarse |
Ejemplo
app.post('/webhooks/allsign', (req, res) => {
const event = req.body.event;
if (event === 'signer.signed') {
const { signer, progress } = req.body.data;
console.log(`${signer.name} firmó — ${progress.completed}/${progress.total}`);
}
if (event === 'document.completed') {
const pdf = req.body.pdf;
downloadSignedPdf(pdf.s3_presigned_url);
markContractCompleted(req.body.document.id);
}
res.status(200).json({ received: true });
});
Para la referencia completa de webhooks (todos los eventos, payloads, seguridad HMAC), consulta la documentación de Webhooks.
3. Return URLs (metadata para redirección)
Si configuraste success_url o cancel_url al crear la Signing Session, AllSign las incluye como metadata en los callbacks onSuccess y onExit. AllSign no redirige automáticamente — tú controlas el timing y la UX del redirect en tu propio código.
Cómo llegan las URLs
onSuccess: (result) => {
// result.success_url viene si la configuraste en POST /v2/signing-sessions
showThankYouMessage();
if (result.success_url) {
// Tú decides cuándo redirigir
setTimeout(() => {
window.location.href = result.success_url;
}, 2000);
}
}
onExit: (metadata) => {
if (metadata.reason === 'cancelled' && metadata.cancel_url) {
window.location.href = metadata.cancel_url;
}
}
Configurar las URLs
Las URLs se configuran al crear la sesión:
const session = await fetch('https://api.allsign.io/v2/signing-sessions', {
method: 'POST',
headers: { 'Authorization': 'Bearer allsign_live_sk_...', 'Content-Type': 'application/json' },
body: JSON.stringify({
document_id: '550e8400-...',
signer_email: 'juan@empresa.com',
success_url: 'https://tuapp.com/firmado', // opcional
cancel_url: 'https://tuapp.com/cancelado', // opcional
})
});
¿Por qué no redirigimos automáticamente? Porque el iframe no puede navegar al parent (cross-origin security), y forzar un redirect te quita control del UX. Tú puedes mostrar una pantalla de éxito, hacer animaciones, llamar a /verify para obtener la evidencia, y luego redirigir cuando sea el momento correcto.
SSE: Eventos en tiempo real (avanzado)
AllSign también ofrece Server-Sent Events (SSE) para actualizaciones en tiempo real desde tu backend — sin polling y con menos latencia que webhooks.
// 1. Obtén un token SSE desde tu backend
const { token } = await fetch('/api/sse-token', {
method: 'POST',
body: JSON.stringify({ documentId })
}).then(r => r.json());
// 2. Conéctate al stream
const eventSource = new EventSource(
`https://api.allsign.io/v2/sse/stream?token=${token}`
);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('SSE:', data.type);
// 'signer_signed', 'document_completed', 'status_changed'
};
Patrón recomendado: Callbacks + Webhooks
La integración más robusta usa callbacks para la UI y webhooks para el backend:
// Frontend: feedback instantáneo
const session = AllSign.init({
clientSecret,
onSuccess: () => {
showConfirmation(); // Instantáneo — el usuario ve que firmó
},
onEvent: (e) => {
trackAnalytics(e); // Cada paso del flujo
},
});
session.modal();
// Backend: fuente de verdad
//
// Ojo: los dos eventos usan envelopes distintos.
// document.completed → { event, document:{...}, pdf:{...} } (sin clave `data`)
// signer.signed → { event, data:{ document, signer, progress } }
app.post('/webhooks/allsign', (req, res) => {
// El cierre del documento: PDF final + acción de negocio.
if (req.body.event === 'document.completed') {
downloadPdf(req.body.pdf.s3_presigned_url); // PDF final
activateService(req.body.document.id); // Acción de negocio
// ⚠️ document.completed NO trae `data.signer`. Para notificar a un
// firmante específico usa el evento signer.signed (ver abajo).
}
// Notificación por cada firmante: aquí sí existe data.signer.
if (req.body.event === 'signer.signed') {
sendConfirmationEmail(req.body.data.signer); // Notificación
}
res.status(200).json({ received: true });
});
Siguiente paso
- Seguridad — Signing Sessions, CSP y validación
- JS SDK Reference — API completa del SDK
- Errores — Taxonomía completa y patrón de recuperación

