Firma incrustable
Incrusta el flujo de firma directamente en tu aplicación para que los destinatarios puedan firmar documentos sin salir de tu producto. Esto crea una experiencia de firma fluida y de marca blanca.
Patrón de la URL de firma
La interfaz de firma está disponible en:
https://app.firma.dev/signing/{signing_request_user_id}
Incrustación básica con iframe
<iframe
src="https://app.firma.dev/signing/{signing_request_user_id}"
style="width:100%;height:900px;border:0;"
allow="camera;microphone;clipboard-write"
title="Document Signing"
></iframe>
Permisos de iframe requeridos
camera - Para verificación de identidad (si está habilitada)
microphone - Para verificación por video (si está habilitada)
clipboard-write - Para copiar/pegar contenido
Obtener la URL de firma
El signing_request_user_id se devuelve cuando obtienes los usuarios de la Solicitud de Firma a través de la API.
Ejemplo: obtener las URL de firma de los destinatarios
const response = await fetch(
`https://api.firma.dev/functions/v1/signing-request-api/signing-requests/${signingRequestId}/users`,
{
headers: {
'Authorization': `Bearer ${API_KEY}`
}
}
)
const users = await response.json()
// Each user has their own signing URL
users.forEach(user => {
const signingUrl = `https://app.firma.dev/signing/${user.id}`
console.log(`${user.email}: ${signingUrl}`)
})
Ejemplo de implementación completa
Componente React
import { useState, useEffect } from 'react'
function SigningView({ signingRequestId, recipientEmail }) {
const [signingUrl, setSigningUrl] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
async function loadSigningUrl() {
try {
// Fetch signing request users from your backend
const response = await fetch(
`/api/signing-requests/${signingRequestId}/users`,
{ credentials: 'include' }
)
if (!response.ok) {
throw new Error('Failed to load signing users')
}
const users = await response.json()
// Find user by email
const user = users.find(
u => u.email === recipientEmail
)
if (!user) {
throw new Error('User not found')
}
const url = `https://app.firma.dev/signing/${user.id}`
setSigningUrl(url)
setLoading(false)
} catch (err) {
setError(err.message)
setLoading(false)
}
}
loadSigningUrl()
}, [signingRequestId, recipientEmail])
if (loading) return <div>Loading signing interface...</div>
if (error) return <div>Error: {error}</div>
return (
<iframe
src={signingUrl}
style={{
width: '100%',
height: '900px',
border: 0
}}
allow="camera;microphone;clipboard-write"
title="Sign Document"
/>
)
}
Endpoint del lado del servidor (Node.js)
import express from 'express'
import fetch from 'node-fetch'
const app = express()
const API_KEY = process.env.FIRMA_API_KEY
app.get('/api/signing-requests/:id/users', async (req, res) => {
try {
const response = await fetch(
`https://api.firma.dev/functions/v1/signing-request-api/signing-requests/${req.params.id}/users`,
{
headers: {
'Authorization': `Bearer ${API_KEY}`
}
}
)
if (!response.ok) {
const error = await response.text()
return res.status(response.status).json({ error })
}
const users = await response.json()
res.json(users)
} catch (error) {
console.error('Failed to fetch signing users:', error)
res.status(500).json({ error: 'Internal server error' })
}
})
app.listen(3000)
Eventos postMessage
El iframe de firma emite eventos postMessage para hacer seguimiento del progreso de la firma:
window.addEventListener('message', (event) => {
// Validate origin
if (event.origin !== 'https://app.firma.dev') return
const data = event.data
switch (data.type) {
case 'signing.started':
console.log('User started signing')
break
case 'signing.completed':
console.log('Document signed successfully')
// Redirect or show success message
break
case 'signing.declined':
console.log('User declined to sign')
break
case 'signing.error':
console.error('Signing error:', data.error)
break
}
})
Buenas prácticas de seguridad
Nunca expongas claves API en el código del frontend. Obtén siempre los ID de usuario de firma a través de un endpoint seguro de backend.
✅ Qué hacer
- ✅ Obtén los ID de usuario de firma a través de tu backend
- ✅ Valida los orígenes de postMessage (
https://app.firma.dev)
- ✅ Usa HTTPS para todas las solicitudes a la API
- ✅ Supervisa los eventos de firma mediante webhooks (60 solicitudes/min)
❌ Qué no hacer
- ❌ No expongas claves API en el código del cliente
- ❌ No confíes en los datos de postMessage sin validar el origen
Límites de solicitudes
Consulta la guía sobre Límites de solicitudes.
Integración de webhooks
Usa webhooks para hacer seguimiento de los eventos de firma en tiempo real en lugar de hacer polling:
// Webhook handler
app.post('/webhooks/firma', async (req, res) => {
const event = req.body
switch (event.event_type) {
case 'signing_request.viewed':
await notifyUser(event.data.signing_request_id, 'viewed')
break
case 'signing_request.recipient.signed':
await notifyUser(event.data.signing_request_id, 'signed')
break
case 'signing_request.completed':
await markComplete(event.data.signing_request_id)
break
}
res.json({ received: true })
})
Consulta la guía de Webhooks para conocer los detalles completos de implementación.
Solución de problemas
El iframe no carga
Posibles causas:
signing_request_user_id inválido
- El destinatario ya completó la firma
- La Solicitud de Firma fue cancelada o expiró
Solución: Verifica el estado de la Solicitud de Firma a través de la API
No se reciben eventos postMessage
Posibles causas:
- La validación de origen está bloqueando los mensajes
- El event listener no se adjuntó antes de que cargara el iframe
Solución:
- Verifica que el origen sea exactamente
https://app.firma.dev
- Adjunta el listener antes de crear el iframe
403 Forbidden al obtener la Solicitud de Firma
Posibles causas:
- El usuario no tiene acceso a la Solicitud de Firma
- La clave API no tiene los permisos requeridos
Solución: Verifica la lógica de autorización y los permisos de la clave API
Próximos pasos