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
- Tu servidor solicita un token JWT a la API de Firma usando tu clave API
- Firma devuelve un token JWT de corta duración con el ID de la solicitud de firma
- Tu frontend incrusta el editor con el token JWT
- 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ón | Tipo | Descripción |
|---|
container | HTMLElement | Elemento DOM donde se monta el editor (obligatorio) |
jwt | string | Token de autenticación de tu backend (obligatorio) |
signingRequestId | string | Identificador de la solicitud de firma (obligatorio) |
theme | 'dark' | 'light' | Tema del editor (predeterminado: 'dark') |
readOnly | boolean | Habilita el modo de solo lectura (predeterminado: false) |
height | string | Altura del contenedor (predeterminado: '100vh') |
width | string | Ancho del contenedor (predeterminado: '100%') |
showCloseButton | boolean | Muestra un botón de cierre en el encabezado del editor (predeterminado: false) |
onSave | function | Callback cuando se guarda la solicitud de firma |
onSend | function | Callback cuando se envía la solicitud de firma |
onClose | function | Callback cuando se hace clic en el botón de cierre o se llama a triggerClose(). Recibe { hasUnsavedChanges: boolean } |
onError | function | Callback para el manejo de errores |
onLoad | function | Callback cuando el editor carga con los datos de la solicitud de firma |
Métodos de instancia
| Método | Descripció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