Errores del flujo embebido
Taxonomía completa de los códigos de error que puede recibir tu integración embebida: qué significa cada uno, quién lo emite, si es terminal o reintentable, y qué hacer.
Esta página cubre los errores del flujo embebido (códigos string como SESSION_EXPIRED). Los errores de la API REST v2 usan otro formato (códigos numéricos E1XXX) — esos viven en Errores de la API.
Cómo te llegan los errores
Hay dos canales, según la capa donde ocurre el error:
-
Excepción síncrona de
AllSign.init()— errores de validación local del SDK (secret faltante, formato inválido, secret key en el browser). Se lanzan comoAllSignErrorcon uncodeestable. -
Callback
onExitconmetadata.error.code— errores en tiempo de ejecución dentro del iframe (init contra el backend, firma, verificación de identidad). El iframe emiteallsign:errory el SDK te lo entrega verbatim enonExit— el SDK no normaliza códigos, así que puedes recibir códigos que tu versión del SDK aún no cataloga.
import { AllSign, AllSignError, isRetryableError } from '@allsign/embedded';
try {
const session = AllSign.init({
clientSecret,
onExit: (metadata) => {
const code = metadata.error?.code;
if (!code) return; // salida limpia (completada o cancelada por el firmante)
console.error('Error embebido:', code, metadata.error?.message);
},
});
session.modal();
} catch (e) {
if (e instanceof AllSignError) {
// Error de validación local — corrige tu integración
console.error(e.code, e.message);
}
}
Terminal vs reintentable
Cada código cae en una de dos clases, y la clase determina la recuperación:
-
Reintentable (transitorio) — la falla es de infraestructura, no de la sesión. El mismo
client_secretsigue siendo válido y re-intercambiable durante toda su ventana de 15 minutos: re-invocaAllSign.initcon él (idealmente con backoff). Son reintentables:NETWORK_ERROR,BACKEND_UNREACHABLE,INVALID_BACKEND_RESPONSE,INTERNAL_ERROR,INIT_TIMEOUTy cualquierHTTP_5xx. -
Terminal — la sesión quedó inutilizable (expiró, se completó, se canceló, es inválida o está mal configurada). Reintentar con el mismo
client_secretno puede funcionar. Recuperación: tu servidor crea una sesión nueva y tu frontend re-invocainit(ver Recuperación). Todo código que no esté en la lista reintentable — incluidos códigos desconocidos — es terminal.
Desde la versión 0.3.0 del SDK, los helpers isRetryableError(code) / isTerminalError(code) implementan exactamente esta clasificación (incluyendo el patrón HTTP_5xx):
import { isRetryableError, isTerminalError } from '@allsign/embedded';
isRetryableError('BACKEND_UNREACHABLE'); // true
isRetryableError('HTTP_503'); // true
isTerminalError('SESSION_EXPIRED'); // true
isTerminalError('CODIGO_FUTURO'); // true — default seguro
Catálogo completo
| Código | HTTP | Dónde se origina | Tipo | Acción recomendada |
|---|---|---|---|---|
MISSING_CLIENT_SECRET | — / 400 | SDK (AllSign.init) y proxy de init | Terminal | Obtén el client_secret de tu backend (POST /v2/signing-sessions) antes de llamar init. |
INVALID_CLIENT_SECRET_FORMAT | — | SDK (AllSign.init) | Terminal | Pasa el client_secret tal cual lo devuelve la API — sin truncar ni re-codificar. |
SECRET_KEY_PASSED_AS_CLIENT_SECRET | — | SDK (AllSign.init) | Terminal | Bug de seguridad en tu integración: la secret key nunca debe llegar al navegador. Crea la sesión server-side y manda solo el client_secret. |
INVALID_REQUEST | 400 | Proxy de init (dashboard) | Terminal | El body no era JSON válido. Solo ocurre llamando el endpoint a mano — usa el SDK. |
BACKEND_UNREACHABLE | 502 | Proxy de init (dashboard) | Reintentable | Reintenta init con el mismo client_secret (con backoff). Si persiste, reporta a soporte. |
INVALID_BACKEND_RESPONSE | passthrough (usualmente 5xx) | Proxy de init (dashboard) | Reintentable | El backend respondió algo no-JSON. Reintenta con el mismo client_secret. |
SESSION_INVALID | 400 / 500 | Backend (init) | Terminal | El secret no corresponde a ninguna sesión (o la sesión está corrupta). Crea una sesión nueva; verifica que no mezclas environments (test vs live). |
SESSION_EXPIRED | 410 | Backend (init) / iframe en flujo | Terminal | El client_secret venció (TTL 15 min). Crea la sesión justo antes de montar el widget, no al cargar la página. |
SESSION_COMPLETED | 409 | Backend (init) | Terminal | El documento ya se firmó. Trátalo como éxito en tu UX; para otra firma, crea una sesión nueva. |
SESSION_CANCELLED | 409 | Backend (init) | Terminal | La sesión fue anulada server-side. Crea una sesión nueva si la firma debe proceder. |
ORIGIN_NOT_ALLOWED | 403 | Backend (init) | Terminal | Agrega el dominio de TU app a allowed_origins al crear la sesión. http://localhost siempre está permitido en desarrollo. |
RATE_LIMITED | 429 | Backend (init) | Terminal | Demasiados intentos de init desde la misma IP (10/min). Espera ~1 minuto antes de reintentar — no hagas loop. |
INTERNAL_ERROR | 500 | Backend (init) | Reintentable | Error inesperado del lado de AllSign. Reintenta con el mismo client_secret; si persiste, crea sesión nueva y reporta a soporte. |
SESSION_NOT_FOUND | 404 | Backend (GET /v2/signing-sessions/{id}/policy) | Terminal | El session_id no existe (o está mal formado). Verifícalo; crea una sesión nueva si ya no existe. |
ALLOWED_ORIGINS_REQUIRED | 400 | Backend (POST /v2/signing-sessions) — lo ve TU servidor | Terminal | Las sesiones live requieren allowed_origins. Incluye el dominio donde montas el iframe (el de tu app, no el de AllSign). |
SESSION_NOT_COMPLETED | 409 | Backend (POST /v2/signing-sessions/{id}/verify) — lo ve TU servidor | Terminal | Llamaste verify antes de que el firmante terminara. Hazlo después de onSuccess o del webhook document.completed. |
NOT_EMBEDDED | — | Página del iframe | Terminal | Alguien cargó /embed/sign/… directo en una ventana. No enlaces esa URL — monta el flujo con el SDK. |
NETWORK_ERROR | — | Iframe / SDK (fetch del navegador) | Reintentable | Reintenta init con el mismo client_secret. Si persiste, revisa la conectividad del firmante y tu CSP (connect-src). |
INVALID_RESPONSE | — | Iframe (respuesta no-JSON) | Terminal | Reintenta una vez con un init nuevo; si se reproduce, reporta a soporte. |
INIT_TIMEOUT | — | Iframe (handshake / init) | Reintentable | Re-invoca init con el mismo secret. Si persiste, verifica que nada en tu página bloquee postMessage hacia/desde el iframe. |
NO_FIELDS | — | Iframe (carga) / acción de firma | Terminal | El documento está en modo autógrafa pero no tiene campos de firma. Corrígelo server-side y crea una sesión nueva. |
HTTP_{status} | el status | Iframe (fallback sin código legible) | 5xx reintentable / 4xx terminal | HTTP_502 → reintenta; HTTP_404 → trata como terminal. isRetryableError ya distingue el patrón. |
WRONG_SIGNING_MODE | — | Acción de firma (iframe) | Terminal | El documento usa Firma Simple y se enviaron campos de firma. Crea la sesión sobre un documento configurado correctamente. |
SUBMIT_FAILED | — | Acción de firma (iframe) | Terminal* | El iframe reintenta inline con el firmante, así que rara vez te llega. Si te llega: sesión nueva; si se repite, reporta con el sessionId. |
SIGNATURE_NOT_FOUND | — | Acción de pipeline (iframe) | Terminal | La firma referenciada ya no existe (p. ej. documento borrado). Verifica el documento y crea una sesión nueva. |
PIPELINE_FETCH_FAILED | — | Acción de pipeline (iframe) | Terminal | No se pudieron cargar los pasos de verificación. Sesión nueva; reporta si es reproducible. |
MODULE_UNSUPPORTED | — | Pipeline de verificación (iframe) | Terminal | La sesión exige un módulo de verificación que el flujo embebido no puede renderizar (y no se puede saltar). Ajusta los requisitos del documento o usa el link de firma no-embebido. |
VERIFICATION_FAILED | — | Pipeline de verificación (iframe) | Terminal | Un paso de verificación de identidad falló. Es un resultado del firmante, no un bug — decide tu flujo (nuevo intento con sesión nueva, o revisión manual). |
VERIFICATION_EXPIRED | — | Pipeline de verificación (iframe) | Terminal | Un paso de verificación expiró antes de completarse. Crea una sesión nueva para que el firmante verifique de nuevo. |
CSP_BLOCKED | — | SDK | Terminal | Tu Content-Security-Policy bloqueó el iframe. Agrega https://allsign.io a frame-src. |
CAMERA_DENIED | — | SDK / iframe | Terminal | El firmante negó el permiso de cámara (necesario para IDV). Pídele habilitarlo y arranca un intento nuevo. |
NOT_IMPLEMENTED | — | SDK | Terminal | La feature aún no está en tu versión del SDK. Actualiza @allsign/embedded. |
Los códigos marcados con "—" en HTTP no viajan como respuesta HTTP hacia ti: nacen en el navegador (SDK o iframe) y te llegan por onExit / excepción. ALLOWED_ORIGINS_REQUIRED y SESSION_NOT_COMPLETED son la excepción inversa: los ve tu servidor al llamar la API, antes o después de que el SDK participe.
Recuperación
El patrón de recuperación es el mismo que usa Plaid Link: ante un error terminal, no "revivas" la sesión — crea una nueva. Las Signing Sessions son baratas de crear y el client_secret dura 15 minutos.
- Error reintentable → re-invoca
AllSign.initcon el mismoclient_secret(sigue siendo re-intercambiable dentro de su ventana de 15 min; el backend rota el token interno y devuelve uno fresco). Usa un backoff corto. - Error terminal → tu servidor crea una sesión nueva con
POST /v2/signing-sessions(mismodocument_id, mismo firmante) y tu frontend re-invocainitcon elclient_secretfresco.
import { AllSign, isRetryableError } from '@allsign/embedded';
function montarFirma(clientSecret) {
const session = AllSign.init({
clientSecret,
onSuccess: (result) => actualizarUI(result),
onExit: async (metadata) => {
const code = metadata.error?.code;
if (!code) return; // salida limpia — no hay nada que recuperar
if (isRetryableError(code)) {
// Transitorio: el MISMO client_secret sigue siendo válido
setTimeout(() => montarFirma(clientSecret), 2000);
return;
}
// Terminal: pide a TU backend una sesión NUEVA y re-monta
const res = await fetch('/mi-api/signing-sessions', {
method: 'POST',
body: JSON.stringify({ documentId: miDocumentId }),
});
const { client_secret: fresco } = await res.json();
montarFirma(fresco);
},
});
session.modal();
}
Crea la sesión justo antes de montar el widget, no al cargar la página. Si la creas al inicio y el usuario tarda en llegar al paso de firma, llegará con media ventana de 15 minutos ya consumida — y SESSION_EXPIRED será tu error más frecuente sin que nada esté roto.
Casos que NO debes reintentar en automático:
SESSION_COMPLETED— ya está firmado; muéstralo como éxito.RATE_LIMITED— espera ~1 minuto; reintentar en loop solo alarga el bloqueo.SECRET_KEY_PASSED_AS_CLIENT_SECRET,ORIGIN_NOT_ALLOWED,ALLOWED_ORIGINS_REQUIRED,CSP_BLOCKED,NOT_EMBEDDED— son errores de configuración de tu integración; ningún reintento los arregla.VERIFICATION_FAILED— es un resultado de identidad del firmante; decide tu flujo de producto (reintento manual, revisión).
Reporte a soporte
Si un error persiste después de aplicar el patrón de recuperación, repórtalo con estos datos — aceleran el diagnóstico de días a minutos:
| Dato | Dónde lo obtienes |
|---|---|
code y message | metadata.error en onExit (o la excepción de init) |
sessionId | metadata.sessionId en onExit, o el session_id de POST /v2/signing-sessions |
| Timestamp (con zona horaria) | El momento del fallo — nos permite correlacionar logs del backend |
| Origen donde montas el iframe | window.location.origin de tu app |
| Versión del SDK | npm ls @allsign/embedded |
| ¿Reproducible? | Si pasa siempre, a veces, o pasó una vez |
Canales: GitHub Issues del SDK para bugs del SDK, o tu canal de soporte de AllSign para errores de sesión/backend (incluye el sessionId — sin él no podemos rastrear la sesión).
Nunca incluyas el client_secret ni tu secret key en un reporte. El sessionId es suficiente para rastrear la sesión y no es un secreto.
Siguiente paso
- JS SDK Reference — API completa del SDK, incluidos
isRetryableError/isTerminalError - Eventos y Callbacks —
onExit,onEventy el shape deExitMetadata - Seguridad —
allowed_origins, CSP y validación de sesiones

