> ## Documentation Index
> Fetch the complete documentation index at: https://docs.firma.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Firma incrustable

> Incrusta la experiencia de firma en tu producto para que los firmantes puedan completar documentos sin salir de tu app.

# 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

```html theme={null}
<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

```js theme={null}
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

```jsx theme={null}
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)

```js theme={null}
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:

```js theme={null}
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

<Warning>
  **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.
</Warning>

### ✅ 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](rate-limits).

## Integración de webhooks

Usa webhooks para hacer seguimiento de los eventos de firma en tiempo real en lugar de hacer polling:

```js theme={null}
// 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](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

* [Envía Solicitudes de Firma](sending-signing-request) con plantillas personalizadas
* [Configura webhooks](webhooks) para notificaciones de eventos en tiempo real (60 solicitudes/min)
* [Configuración del espacio de trabajo](workspace-settings) para personalizar la marca de los correos electrónicos (100-200 solicitudes/min)
