Ir al Panel →

🧩 Widget de Firmas

Componente JavaScript embebible que muestra el estado de firma de un documento directamente en tu aplicación web, con actualización en tiempo real.

Scriptmanyao-widget.js
AuthBearer Token
PollingCada 5s (configurable)
¿Qué hace el Widget?
  • Estado de firma de cada firmante con barra de progreso
  • Código QR para que los firmantes escaneen desde su celular
  • Gestión de firmantes (agregar / eliminar) sin salir de tu app
  • Certificados de firma con foto, prueba de vida y datos biométricos
  • Visor y descarga del PDF original o firmado
  • Firma asistida — modal con cámara en la misma máquina
📡Flujo de Integración
sequenceDiagram participant F as Tu Frontend participant B as Tu Backend participant A as API Manyao participant W as Widget B->>A: POST /v1/auth/token (client_id + client_secret) A-->>B: access_token B->>A: POST /v1/documents (PDF + firmantes) A-->>B: document_uuid F->>B: GET /api/widget-token/:docId B-->>F: { token, documentId } F->>W: script data-document="UUID" data-token="TOKEN" W->>A: GET /v1/widget/{uuid} (polling cada 5s) A-->>W: estado actualizado Note over W: Se actualiza automáticamente cuando los firmantes firman
⚠️ Nunca expongas tu client_id/client_secret en el frontend. El token del widget lo debe obtener tu backend y pasarlo al frontend.
⚙️Instalación
Opción A — Carga estática (HTML)
<!-- Contenedor del widget -->
<div id="manyao-widget"></div>

<!-- Script del widget -->
<script src="https://manyao.pe/widget/manyao-widget.js"
        data-document="UUID_DEL_DOCUMENTO"
        data-token="BEARER_TOKEN"
        data-container="manyao-widget">
</script>
Opción B — Carga dinámica (JavaScript)
async function loadManyaoWidget(docUuid) {
  // 1. Tu backend obtiene el Bearer token
  const resp = await fetch('/tu-api/manyao-token');
  const { token } = await resp.json();

  // 2. Cargar widget dinámicamente
  const script = document.createElement('script');
  script.src = 'https://manyao.pe/widget/manyao-widget.js';
  script.dataset.document = docUuid;
  script.dataset.token = token;
  script.dataset.container = 'manyao-widget';
  document.body.appendChild(script);
}

loadManyaoWidget('tu-document-uuid');
🎛️Atributos de Configuración
Requeridos
AtributoTipoDescripción
data-documentstringUUID del documento a mostrar
data-tokenstringBearer token de lectura — no crea documentos ni consume créditos
Configuración básica
AtributoTipoDescripciónDefault
data-containerstringID del elemento HTML donde renderizarmanyao-widget
data-apistringURL base de la API del widgethttps://manyao.pe/v1/widget
data-sign-urlstringURL base para enlaces de firma y QRhttps://manyao.whatsign.com
data-pollnumberIntervalo de polling en ms5000
Apariencia visual
AtributoDescripciónDefault
data-accentColor de acento CSS (hex, rgb…). Afecta botones, badges, bordes y panel QR#0F766E
data-title-sizeTamaño de fuente del título del documentoinherit
data-title-colorColor del título del documentoinherit
data-subtitle-sizeTamaño de fuente del subtítulo / metadatosinherit
data-subtitle-colorColor del subtítulo / metadatosinherit
Visibilidad de controles
AtributoDescripciónDefault
data-qr-openPanel QR abierto al cargar. false para empezar colapsadotrue
data-show-add-signerMostrar botón "Agregar firmante"true
data-show-remove-signerMostrar botón eliminar firmante en cada filatrue
data-show-reopenMostrar botón "Reabrir" en documentos expirados o parcialestrue
data-show-partialMostrar botón de firma parcial cuando hay firmantes pendientestrue
data-show-powered-byMostrar el badge "Powered by Manyao" al pie del widgettrue
data-show-metadata-valueMostrar valores de metadata del firmante como badges junto al nombrefalse
Etiquetas personalizables
AtributoDescripciónDefault
data-label-partialTexto del botón de firma parcialFirma parcial
data-label-reopenTexto del botón reabrir documentoReabrir
data-label-qr-tabTexto del tab/panel del código QRFirmar participantes
💻Backend — Endpoint del Token (Node.js)
// GET /api/widget-token/:docId
app.get('/api/widget-token/:docId', async (req, res) => {
  const auth = await fetch('https://api.manyao.pe/v1/auth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      client_id: process.env.MANYAO_CLIENT_ID,
      client_secret: process.env.MANYAO_CLIENT_SECRET,
      grant_type: 'client_credentials'
    })
  });
  const { access_token } = await auth.json();
  res.json({ token: access_token, documentId: req.params.docId });
});
🐘Backend — Endpoint del Token (PHP)
// GET /api/widget-token.php?doc=UUID
$ch = curl_init('https://api.manyao.pe/v1/auth/token');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode([
        'client_id'     => getenv('MANYAO_CLIENT_ID'),
        'client_secret' => getenv('MANYAO_CLIENT_SECRET'),
        'grant_type'    => 'client_credentials'
    ]),
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
]);
$resp = json_decode(curl_exec($ch), true);

header('Content-Type: application/json');
echo json_encode([
    'token'      => $resp['access_token'],
    'documentId' => $_GET['doc']
]);
📡Eventos — Interceptar el PollingNEW

En cada ciclo de polling el widget emite un evento manyao:status con el estado completo del documento y los firmantes. Puedes interceptarlo de dos formas según cómo hayas embebido el widget.

Opción A — Widget embebido directo (mismo documento)
document.addEventListener('manyao:status', (event) => {
  const { document, signers, session, changed } = event.detail;

  console.log('Estado documento:', document.status);
  console.log('Firmantes:', signers);

  // Solo actuar si hubo cambios reales en este poll
  if (changed) {
    actualizarMiUI(document, signers);
  }
});
Opción B — Widget dentro de un iframe
window.addEventListener('message', (event) => {
  // Verificar que el mensaje es de manyao
  if (event.data?.type !== 'manyao:status') return;

  const { document, signers, session, changed } = event.data;

  console.log('Estado documento:', document.status);
  console.log('Firmantes:', signers);

  if (changed) {
    actualizarMiUI(document, signers);
  }
});
💡 El evento se emite en cada poll (por defecto cada 5s). El flag changed: true indica que hubo cambios reales respecto al poll anterior — úsalo para evitar re-renders innecesarios.
Estructura del payload (event.detail o event.data)
CampoTipoDescripción
typestringSiempre "manyao:status"
documentIdstringUUID del documento
documentobjectEstado completo del documento — ver tabla abajo
signersarrayLista de firmantes — ver tabla abajo
sessionobjectEstado del token de sesión — ver tabla abajo
changedbooleantrue si hubo cambios desde el poll anterior (estado, firmantes o PDF)
Objeto document
CampoTipoDescripción
uuidstringUUID del documento
titlestringTítulo del documento
companystringNombre de la empresa emisora
statusstringEstado del documento: pending · signing · completed · partial · cancelled · expired
totalnumberTotal de firmantes del documento
completednumberCantidad de firmantes que completaron la firma
can_generate_pdfbooleantrue si todos los firmantes completaron y se puede generar el PDF final
has_signed_pdfbooleantrue si el PDF firmado ya fue generado y está disponible
pdf_generatingbooleantrue si el PDF está siendo generado en este momento
has_pdfbooleantrue si existe un PDF original adjunto al documento
pdf_size_formattedstring|nullTamaño del PDF original formateado (ej: "369 KB")
page_countnumber|nullNúmero de páginas del PDF
created_atstringFecha de creación ISO 8601
expires_atstring|nullFecha de expiración ISO 8601, o null si no expira
signing_modestringclosed (lista cerrada) · open (cualquiera puede firmar)
signing_blockedbooleantrue si las nuevas firmas están bloqueadas temporalmente
allow_inspector_assistbooleantrue si se permite la firma asistida por inspector
Objeto dentro de signers[]
CampoTipoDescripción
idnumberID interno del firmante
dnistringNúmero de documento del firmante
doc_typestringTipo de documento: dni-pe · pe-carnet · ci-pe · dni-pa
namestring|nullNombre completo verificado biométricamente, o null si aún no firmó
titlestring|nullCargo o rol del firmante (opcional, definido al crear)
metadataobject|nullDatos extra del firmante definidos al crear el documento
statusstringpending · identifying · signing · waiting_review · completed · rejected · declined
signed_atstring|nullTimestamp ISO 8601 de cuando firmó, o null si aún no completó
signer_stepstring|nullPaso actual dentro del flujo de firma (uso interno)
step_sincestring|nullTimestamp de cuando entró al paso actual
Objeto session
CampoTipoDescripción
token_expiredbooleantrue si el Bearer token expiró — el widget pasa a modo solo lectura
token_expires_atstring|nullFecha de expiración del token ISO 8601
read_onlybooleantrue si la sesión está en modo solo lectura (token expirado o documento en estado terminal)
actions_allowedstring[]Acciones disponibles: view_document · download_pdf · add_signer · remove_signer · share_qr · share_link
actions_blockedstring[]Acciones bloqueadas en esta sesión
Ejemplo completo — reaccionar al completarse todos los firmantes
document.addEventListener('manyao:status', (event) => {
  const { document, signers, changed } = event.detail;

  // Actualizar contador en tu UI
  document.getElementById('progreso').textContent =
    `${document.completed} de ${document.total} firmantes`;

  // Detectar cuando se completó el documento
  if (document.status === 'completed' && document.has_signed_pdf) {
    document.getElementById('descargar').style.display = 'block';
    mostrarNotificacion('¡Documento firmado por todos!');
  }

  // Detectar firmante en revisión manual
  const enRevision = signers.find(s => s.status === 'waiting_review');
  if (enRevision) {
    mostrarAlerta(`${enRevision.name || enRevision.dni} requiere revisión manual`);
  }
});
Eventos — Acciones del WidgetNEW

El widget emite el evento manyao:action cada vez que el emisor realiza una acción (aprobar, agregar firmante, descargar, etc.). A diferencia de manyao:status que se emite en cada poll, este evento es puntual — se dispara solo cuando ocurre la acción.

Opción A — Widget embebido directo
document.addEventListener('manyao:action', (event) => {
  const { action, documentId, document, signers, data } = event.detail;

  switch (action) {
    case 'signer_added':
      console.log('Firmante agregado:', data.dni);
      break;
    case 'signer_removed':
      console.log('Firmante eliminado:', data.signer_id);
      break;
    case 'signer_review_approve':
      console.log('Firmante aprobado:', data.signer_id);
      break;
    case 'document_download':
      console.log('Documento descargado');
      break;
  }
});
Opción B — Widget dentro de un iframe
window.addEventListener('message', (event) => {
  if (event.data?.type !== 'manyao:action') return;

  const { action, documentId, document, signers, data } = event.data;
  console.log('Acción:', action, data);
});
💡 El payload incluye siempre document y signers con el estado actual en el momento de la acción — no necesitas hacer un poll adicional.
Payload del evento manyao:action
CampoTipoDescripción
typestringSiempre "manyao:action"
actionstringIdentificador de la acción — ver tabla abajo
documentIdstringUUID del documento
documentobjectEstado del documento en el momento de la acción
signersarrayLista de firmantes en el momento de la acción
dataobjectDatos adicionales específicos de la acción — ver tabla abajo
Catálogo de acciones
actionCuándo se emiteCampos en data
signer_review_approveFirmante aprobado desde revisión manualsigner_id, action: "approve"
signer_review_rejectFirmante rechazado desde revisión manualsigner_id, action: "reject"
signer_review_retryFirmante reiniciado desde revisión manualsigner_id, action: "retry"
signer_resetFirmante reiniciado manualmentesigner_id
signer_addedNuevo firmante agregado al documentodoc_type, dni
signer_removedFirmante eliminado del documentosigner_id
document_viewSe abrió el visor del PDF{}
document_downloadSe descargó el PDF del documento{}
signer_pdf_downloadSe descargó el certificado de un firmantesigner_id
cert_viewSe abrió el visor del certificado{}
document_reopenDocumento reactivado/reabiertoextend_hours
Ejemplo completo — escuchar todos los eventos
// Escuchar cambios de estado (polling)
document.addEventListener('manyao:status', (e) => {
  const { document, signers, changed } = e.detail;
  if (changed) actualizarUI(document, signers);
});

// Escuchar acciones puntuales del emisor
document.addEventListener('manyao:action', (e) => {
  const { action, data, document, signers } = e.detail;

  if (action === 'signer_added') {
    mostrarToast(`Firmante ${data.dni} agregado`);
    actualizarUI(document, signers);
  }

  if (action === 'signer_review_approve') {
    mostrarToast('Firmante aprobado ✅');
    actualizarUI(document, signers);
  }

  if (action === 'document_download') {
    registrarDescarga(document.uuid);
  }
});
manyao.pe — Firma digital con validación biométrica