Saltar al contenido principal

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