Saltar al contenido principal

Editor de solicitudes de firma incrustable

Incrusta el editor de solicitudes de firma de Firma dentro de tu aplicación usando autenticación JWT para un acceso seguro y de tiempo limitado. Esto es ideal para integraciones de marca blanca y aplicaciones multiempresa.

Casos de uso

  • Flujos de firma de marca blanca: gestiona solicitudes de firma bajo tu propia marca
  • Aplicaciones multiempresa: acceso seguro por firmante sin exponer claves API
  • Flujos de trabajo de documentos incrustados: gestión fluida de solicitudes de firma dentro de tu producto
  • Acceso de tiempo limitado: los tokens caducan automáticamente por seguridad (expiración de 7 días)

Cómo funciona

  1. Tu servidor solicita un token JWT a la API de Firma usando tu clave API
  2. Firma devuelve un token JWT de corta duración con el ID de la solicitud de firma
  3. Tu frontend incrusta el editor con el token JWT
  4. El token caduca automáticamente después de 7 días
Límite de solicitudes: los endpoints JWT admiten 100 solicitudes por minuto por clave API para aplicaciones de alto volumen.

Autenticación JWT

Generar token JWT

Genera un token JWT para una solicitud de firma específica usando el endpoint /jwt/generate-signing-request. Endpoint: POST /jwt/generate-signing-request Cuerpo de la solicitud:
{
  "companies_workspaces_signing_requests_id": "123e4567-e89b-12d3-a456-426614174000"
}
Respuesta (200 OK):
{
  "jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "jwt_id": "jwt123-e89b-12d3-a456-426614174000",
  "expires_at": "2024-04-27T10:00:00Z",
  "signing_request_id": "123e4567-e89b-12d3-a456-426614174000",
  "created_at": "2024-04-20T10:00:00Z"
}
Encabezados de límite de solicitudes:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1729180800

Guía de implementación

Seguridad: nunca expongas tu clave API en código del lado del cliente. Genera siempre los tokens JWT desde tu backend seguro.

Backend: generar token JWT

Llama a la API de Firma desde tu backend para generar un token JWT. Tu endpoint de backend debe aceptar un ID de solicitud de firma y devolver el JWT a tu frontend. Ejemplo en Node.js:
// Example: Simple backend function to generate JWT
async function generateSigningRequestToken(signingRequestId) {
  const response = await fetch('https://api.firma.dev/functions/v1/signing-request-api/jwt/generate-signing-request', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.FIRMA_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      companies_workspaces_signing_requests_id: signingRequestId
    })
  })
  
  const data = await response.json()
  return data.jwt
}
Ejemplo en Python:
import os
import requests

def generate_signing_request_token(signing_request_id):
    response = requests.post(
        'https://api.firma.dev/functions/v1/signing-request-api/jwt/generate-signing-request',
        headers={
            'Authorization': f'Bearer {os.getenv("FIRMA_API_KEY")}',
            'Content-Type': 'application/json'
        },
        json={
            'companies_workspaces_signing_requests_id': signing_request_id
        }
    )
    data = response.json()
    return data['jwt']

Implementación frontend — HTML / JavaScript nativo

<!-- Load the Firma Signing Request Editor library -->
<script src="https://api.firma.dev/functions/v1/embed-proxy/signing-request-editor.js"></script>

<!-- Create a container for the editor -->
<div id="signing-request-editor-container"></div>

<script>
  // Assuming you've already fetched the JWT from your backend
  const jwt = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...';
  const signingRequestId = '08f72e3b-79a8-4eac-a268-b3f9efaf6573';
  
  // Initialize the signing request editor
  const editor = new FirmaSigningRequestEditor({
    container: '#signing-request-editor-container',
    jwt: jwt,
    signingRequestId: signingRequestId,
    showCloseButton: true, // Show close button in header
    onSave: (data) => {
      console.log('Signing request saved:', data);
    },
    onSend: (data) => {
      console.log('Signing request sent:', data);
    },
    onClose: (data) => {
      if (data.hasUnsavedChanges) {
        // Prompt user to save before leaving
      }
      console.log('Editor closed, unsaved changes:', data.hasUnsavedChanges);
    },
    onError: (error) => {
      console.error('Editor error:', error);
    },
    onLoad: (signingRequest) => {
      console.log('Editor loaded successfully:', signingRequest);
    }
  });
</script>

Implementación frontend — React

import { useEffect, useRef, useState } from 'react';

function SigningRequestEditorComponent({ signingRequestId, jwt }) {
  const containerRef = useRef<HTMLDivElement>(null);
  const editorRef = useRef<any>(null);
  const [isLoaded, setIsLoaded] = useState(false);

  // Load the Firma Signing Request Editor library
  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://api.firma.dev/functions/v1/embed-proxy/signing-request-editor.js';
    script.async = true;
    
    script.onload = () => setIsLoaded(true);
    document.body.appendChild(script);
    
    return () => {
      if (document.body.contains(script)) {
        document.body.removeChild(script);
      }
    };
  }, []);

  // Initialize the editor when library and JWT are ready
  useEffect(() => {
    if (!isLoaded || !containerRef.current || !jwt) return;

    editorRef.current = new window.FirmaSigningRequestEditor({
      container: containerRef.current,
      jwt: jwt,
      signingRequestId: signingRequestId,
      showCloseButton: true, // Show close button in header
      onSave: (data) => console.log('Saved:', data),
      onSend: (data) => console.log('Sent:', data),
      onClose: (data) => {
        if (data.hasUnsavedChanges) {
          // Prompt user to save before leaving
        }
      },
      onError: (error) => console.error('Error:', error),
      onLoad: (signingRequest) => console.log('Loaded:', signingRequest)
    });

    return () => {
      if (editorRef.current?.destroy) {
        editorRef.current.destroy();
      }
    };
  }, [isLoaded, jwt, signingRequestId]);

  return <div ref={containerRef} className="w-full h-full" />;
}

export default SigningRequestEditorComponent;

Opciones de configuración

OpciónTipoDescripción
containerHTMLElementElemento DOM donde se monta el editor (obligatorio)
jwtstringToken de autenticación de tu backend (obligatorio)
signingRequestIdstringIdentificador de la solicitud de firma (obligatorio)
theme'dark' | 'light'Tema del editor (predeterminado: 'dark')
readOnlybooleanHabilita el modo de solo lectura (predeterminado: false)
heightstringAltura del contenedor (predeterminado: '100vh')
widthstringAncho del contenedor (predeterminado: '100%')
showCloseButtonbooleanMuestra un botón de cierre en el encabezado del editor (predeterminado: false)
onSavefunctionCallback cuando se guarda la solicitud de firma
onSendfunctionCallback cuando se envía la solicitud de firma
onClosefunctionCallback cuando se hace clic en el botón de cierre o se llama a triggerClose(). Recibe { hasUnsavedChanges: boolean }
onErrorfunctionCallback para el manejo de errores
onLoadfunctionCallback cuando el editor carga con los datos de la solicitud de firma

Métodos de instancia

MétodoDescripción
triggerClose()Activa mediante programación el flujo de cierre. Comprueba si hay cambios sin guardar e invoca onClose con { hasUnsavedChanges: boolean }
Usa triggerClose() cuando tu aplicación anfitriona necesite cerrar el editor desde su propia interfaz, por ejemplo, ante un evento de navegación del padre o un botón de “atrás”:
// Close the editor from your app's UI
document.getElementById('my-back-button').addEventListener('click', () => {
  editor.triggerClose();
});

Eventos postMessage (editor → anfitrión)

El editor de solicitudes de firma de Firma emitirá eventos postMessage para acciones importantes del ciclo de vida. A continuación se muestra un esquema de eventos recomendado y mínimo que puedes implementar para reaccionar a los guardados y envíos del editor.
Usa estos eventos para seguir la actividad del editor sin realizar llamadas API adicionales, lo que te ayuda a mantenerte dentro de los límites de solicitudes.
Envoltorio del evento (payload de window.postMessage):
{
	"type": "editor.event",
	"event": "signing_request.saved", // or signing_request.sent, signing_request.closed
	"payload": {
		"signing_request_id": "sr_123",
		"updated_at": "2025-09-04T12:34:56Z",
		"status": "draft" // or "sent"
	}
}

Ejemplo de listener en el cliente (JS simple)

window.addEventListener('message', (ev) => {
	// 1) Validate origin
	if (ev.origin !== 'https://app.firma.dev') return
	// 2) Validate shape
	const data = ev.data || {}
	if (data.type !== 'editor.event') return

	switch (data.event) {
		case 'signing_request.saved':
			console.log('Signing request saved', data.payload)
			// Optionally fetch the latest signing request via API
			break
		case 'signing_request.sent':
			console.log('Signing request sent', data.payload)
			// Trigger webhook or notification
			break
		case 'signing_request.closed':
			console.log('Editor closed')
			break
		default:
			break
	}
})

Gestión del ciclo de vida del token

Los tokens JWT se generan con un tiempo de expiración de 7 días para flujos de edición habituales. El editor gestionará automáticamente la caducidad del token.

Caducidad automática

Los tokens JWT caducan automáticamente después de 7 días según la marca de tiempo expires_at. Después de la caducidad:
  • El editor incrustado rechazará el token
  • Se debe solicitar un nuevo token para continuar
  • No se necesita ninguna llamada API: los tokens caducan de forma pasiva

Renovación del token (opcional)

Para sesiones de edición de larga duración, puedes renovar el token antes de su caducidad usando el método updateJWT():
// Generate a new JWT from your backend
const newJwt = await fetch('/your-backend/generate-token').then(r => r.json());

// Update the editor with the new token
editor.updateJWT(newJwt.jwt);

Límite de solicitudes

Los endpoints JWT admiten 100 solicitudes por minuto por clave API. Encabezados de límite de solicitudes:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1729180800
Si superas el límite de solicitudes (respuesta 429):
  • Almacena en caché los tokens y reutilízalos hasta su caducidad (7 días)
  • Implementa un retroceso exponencial para los reintentos
  • Supervisa el encabezado X-RateLimit-Remaining
  • Genera tokens bajo demanda, no de forma anticipada

Buenas prácticas de seguridad

Nunca expongas tu clave API en código del lado del cliente. Genera siempre los tokens JWT desde un endpoint de servidor seguro.

✅ Qué hacer

  • ✅ Genera los tokens desde tu servidor backend
  • ✅ Supervisa los límites de solicitudes (100 solicitudes/minuto)
  • ✅ Usa HTTPS para todas las solicitudes API
  • ✅ Usa readOnly: true para acceso de solo visualización

❌ Qué no hacer

  • ❌ No expongas claves API en código frontend
  • ❌ No reutilices tokens entre sesiones o firmantes
  • ❌ No registres los tokens JWT en logs (riesgo de seguridad)
  • ❌ No compartas tokens entre distintas solicitudes de firma

Solución de problemas

Error de token caducado

Síntoma: el editor muestra “Token expired” o un error de autenticación Solución:
  • Implementa la renovación del token antes de su caducidad (ventana de 7 días)
  • Genera un nuevo token y llama a editor.updateJWT(newToken)
  • Comprueba la sincronización del reloj del sistema

401 Unauthorized

Síntoma: la generación del JWT falla con un 401 Posibles causas:
  • Clave API inválida o ausente
  • La clave API no tiene los permisos necesarios
  • La clave API está deshabilitada
Solución: verifica la clave API en el panel y comprueba los permisos

404 Not Found

Síntoma: no se encuentra la solicitud de firma al generar el JWT Posibles causas:
  • El ID de la solicitud de firma no existe
  • La solicitud de firma pertenece a otro espacio de trabajo
  • La solicitud de firma fue eliminada
Solución: verifica el ID de la solicitud de firma y el acceso al espacio de trabajo

Límite de solicitudes superado

Síntoma: 429 Too Many Requests Solución:
  • Implementa el almacenamiento en caché de tokens (la ventana de expiración de 7 días es generosa)
  • Espera al restablecimiento del límite de solicitudes (comprueba el encabezado X-RateLimit-Reset)
  • Implementa lógica de reintentos con retroceso exponencial

El editor no carga

Síntoma: el contenedor está vacío o muestra un indicador de carga indefinidamente Posibles causas:
  • El script no se cargó (comprueba el evento script.onload)
  • JWT o ID de solicitud de firma inválidos
  • Problemas de CORS (comprueba la consola del navegador)
Solución:
  • Verifica que el script se cargue desde https://app.firma.dev/embed/signing-request-editor.js
  • Comprueba que el JWT sea válido y no haya caducado
  • Asegúrate de que el backend devuelva los encabezados CORS correctos

Próximos pasos