> ## 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.

# Patrones de Solicitudes de Firma

> Flujos de trabajo comunes con varios firmantes — secuencial, en paralelo, destinatarios dinámicos y firmante más aprobador — junto con las llamadas a la API y la lógica de webhooks que necesita cada uno.

Esta guía cubre los patrones recurrentes que adoptan los flujos de firma: varios firmantes que firman en orden, firmantes que no necesitan un orden, un segundo firmante cuya identidad no se conoce hasta que el primero actúa, y un firmante que también debe aprobar. Cada patrón a continuación es una variación de [crear y enviar una solicitud de firma](/guides/sending-signing-request), así que lee primero esa guía si aún no lo has hecho.

## Elegir un patrón

| Escenario                                                                    | Patrón                                                           |
| :--------------------------------------------------------------------------- | :--------------------------------------------------------------- |
| Dos o más firmantes, con todos los correos conocidos antes de enviar         | [Firma secuencial](#pattern-sequential-signing-known-recipients) |
| El correo del firmante 2 solo se conoce después de que el firmante 1 termina | [Segundo firmante dinámico](#pattern-dynamic-second-signer)      |
| Varios firmantes, sin que ninguno espere a otro                              | [Firma en paralelo](#pattern-parallel-signing)                   |
| Una misma persona necesita firmar y aprobar                                  | [Firmante + aprobador](#pattern-signer-and-approver)             |

## Patrón: Firma secuencial, destinatarios conocidos

Usa este patrón cuando el correo de cada destinatario se conoce en el momento del envío y los firmantes posteriores solo deben ser notificados una vez que los anteriores terminen. Este es el comportamiento predeterminado: `settings.use_signing_order` es `1` a menos que lo desactives, y los destinatarios firman en orden ascendente de `order`.

<Steps>
  <Step title="Crea los destinatarios con un orden explícito">
    Asigna un `order` a cada destinatario. Los números más bajos firman primero.
  </Step>

  <Step title="Envía la solicitud">
    Usa [`create-and-send`](/api-reference/v01.33.00/signing-requests/create-and-send-signing-request-atomic) para una sola llamada, o [`create`](/api-reference/v01.33.00/signing-requests/create-signing-request) seguido de [`/send`](/api-reference/v01.33.00/signing-requests/send-signing-request) si primero necesitas un paso de revisión.
  </Step>

  <Step title="Solo se notifica por correo al primer firmante">
    Firma envía un correo a los destinatarios con `order: 1` de inmediato. Una vez que terminan, Firma envía automáticamente el correo al siguiente nivel de `order` — tú no controlas esto directamente. Suscríbete a los [webhooks](/guides/webhooks) en lugar de hacer sondeo si quieres seguir cada paso.
  </Step>
</Steps>

```bash theme={null}
curl -X POST "https://api.firma.dev/functions/v1/signing-request-api/signing-requests/create-and-send" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Vendor Agreement",
    "template_id": "tmpl_123",
    "recipients": [
      {
        "first_name": "Alice",
        "last_name": "Johnson",
        "email": "alice@example.com",
        "designation": "Signer",
        "order": 1
      },
      {
        "first_name": "Bob",
        "last_name": "Smith",
        "email": "bob@example.com",
        "designation": "Signer",
        "order": 2
      }
    ]
  }'
```

Bob no recibe ningún correo hasta que Alice completa sus campos. Suscríbete a [`signing_request.recipient.signed`](/guides/webhooks) si quieres seguir cada paso, y a `signing_request.completed` para cuando termine toda la cadena.

<Note>
  Los valores de `order` solo necesitan ordenarse correctamente — no hace falta que sean contiguos. Sin embargo, los valores de `order` empatados (p. ej. `1, 2, 2, 5`) **no** crean un nivel de firma paralelo — solo se notifica a un destinatario por valor de order a la vez. Si necesitas que dos firmantes firmen simultáneamente, usa el [patrón paralelo](#pattern-parallel-signers) con `use_signing_order: false` en su lugar.
</Note>

## Patrón: Segundo firmante dinámico

Este es el caso detrás de la mayoría de los tickets de "cómo agrego un firmante a mitad del flujo": el firmante 1 completa algo — una referencia, un cofirmante, un beneficiario — y solo entonces conoces el correo del firmante 2. El instinto es enviar la solicitud solo con el firmante 1 y luego actualizarla para agregar al firmante 2 una vez que sepas quién es.

<Warning>
  **Esa actualización no es posible en la misma solicitud de firma.** Una vez que se establece `sent_on`, `PATCH`/`PUT /signing-requests/{id}` y `DELETE /signing-requests/{id}` devuelven todos `409 ALREADY_SENT` — una solicitud de firma enviada es completamente inmutable, y eso incluye agregar un nuevo destinatario. No existe ningún endpoint que agregue un destinatario a una solicitud ya enviada, ni que cambie el correo de un destinatario en ella. Consulta [Manejo de errores: 409 ALREADY\_SENT](#error-handling-409-already_sent) más abajo para ver la lista completa de operaciones que esto bloquea.
</Warning>

La solución alternativa es **encadenar dos solicitudes de firma** en lugar de modificar una:

<Steps>
  <Step title="Envía la solicitud n.º 1 solo con el firmante 1">
    Incluye un campo (`type: "text"`, con algún `variable_name` como `next_signer_email`) para que el firmante 1 indique quién es el siguiente firmante. Envíala con `create-and-send`, con exactamente un destinatario.
  </Step>

  <Step title="Espera a que el firmante 1 complete">
    Como la solicitud n.º 1 tiene un solo firmante, `signing_request.completed` se dispara en cuanto termina — no necesitas `signing_request.recipient.signed` en este caso.
  </Step>

  <Step title="Lee el campo que completó el firmante 1">
    El payload del webhook no incluye los valores de los campos, así que llama a [`GET /signing-requests/{id}/fields`](/api-reference/v01.33.00/signing-requests/get-signing-request-fields) y lee `final_value` del campo con el `variable_name` correspondiente.
  </Step>

  <Step title="Crea y envía la solicitud n.º 2 para el firmante 2">
    Usa el correo que acabas de extraer. Esta es una solicitud de firma **nueva**, con su propio `id`.
  </Step>
</Steps>

<Warning>
  Como son dos solicitudes de firma independientes, se generan dos certificados de finalización y dos registros de auditoría independientes — no existe un único certificado que cubra a ambos firmantes. Si un certificado unificado es un requisito indispensable, la única alternativa es recopilar el correo del firmante 2 *antes* de enviar — por ejemplo, mediante un formulario en tu propia aplicación — en lugar de hacerlo a mitad del flujo.
</Warning>

<Note>
  Firma no tiene un campo de metadatos ni de referencia externa en la propia solicitud de firma para vincular la solicitud n.º 1 con la n.º 2. Guarda esa relación (por ejemplo, `original_signing_request_id`) en tu propia base de datos cuando crees la solicitud n.º 2.
</Note>

### Enfoque 1: Segunda solicitud activada por webhook

Usa este enfoque cuando los firmantes reciben invitaciones por correo electrónico y tu backend gestiona la cadena.

<CodeGroup>
  ```js Node.js (Express) theme={null}
  import express from 'express'
  import crypto from 'crypto'

  const app = express()
  const API_KEY = process.env.FIRMA_API_KEY
  const WEBHOOK_SECRET = process.env.FIRMA_WEBHOOK_SECRET
  const API_BASE = 'https://api.firma.dev/functions/v1/signing-request-api'

  app.post('/webhooks/firma',
    express.raw({ type: 'application/json' }),
    async (req, res) => {
      const payload = req.body.toString('utf8')
      if (!verifySignature(payload, req.headers['x-firma-signature'], WEBHOOK_SECRET)) {
        return res.status(401).json({ error: 'Invalid signature' })
      }

      const event = JSON.parse(payload)
      res.status(200).json({ received: true }) // ack immediately

      if (event.type !== 'signing_request.completed') return

      const signingRequestId = event.data.signing_request.id

      // Look up whether this request is one of our "signer 1 only" requests
      const original = await db.pendingChains.findOne({ signing_request_id: signingRequestId })
      if (!original) return // not part of a dynamic-signer chain, ignore

      // Signer 1's field values aren't in the webhook payload — fetch them
      const fieldsResp = await fetch(`${API_BASE}/signing-requests/${signingRequestId}/fields`, {
        headers: { Authorization: `Bearer ${API_KEY}` }
      })
      const { results: fields } = await fieldsResp.json()
      const nextSignerField = fields.find(f => f.variable_name === 'next_signer_email')
      const nextSignerEmail = nextSignerField?.final_value

      if (!nextSignerEmail) {
        console.error(`No next_signer_email captured on ${signingRequestId}`)
        return
      }

      // Create and send request #2 for the dynamically-determined signer
      const createResp = await fetch(`${API_BASE}/signing-requests/create-and-send`, {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          name: `${event.data.signing_request.name} - Second Signer`,
          template_id: original.template_id,
          recipients: [
            {
              first_name: 'Next',
              last_name: 'Signer',
              email: nextSignerEmail,
              designation: 'Signer',
              order: 1
            }
          ]
        })
      })
      const created = await createResp.json()

      // Persist the link between the two requests yourself — Firma doesn't track it
      await db.pendingChains.update(
        { signing_request_id: signingRequestId },
        { $set: { chained_signing_request_id: created.id, resolved_at: new Date() } }
      )
    }
  )

  function verifySignature(payload, signatureHeader, secret) {
    if (!signatureHeader) return false
    const parts = {}
    signatureHeader.split(',').forEach(part => {
      const [key, value] = part.split('=')
      parts[key] = value
    })
    if (!parts.t || !parts.v1) return false
    const signedPayload = `${parts.t}.${payload}`
    const expected = crypto.createHmac('sha256', secret).update(signedPayload).digest('hex')
    try {
      return crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected))
    } catch {
      return false
    }
  }

  app.listen(3000)
  ```

  ```py Python (Flask) theme={null}
  import os
  import hmac
  import hashlib
  import requests
  from flask import Flask, request, jsonify

  app = Flask(__name__)
  API_KEY = os.environ['FIRMA_API_KEY']
  WEBHOOK_SECRET = os.environ['FIRMA_WEBHOOK_SECRET']
  API_BASE = 'https://api.firma.dev/functions/v1/signing-request-api'

  @app.route('/webhooks/firma', methods=['POST'])
  def firma_webhook():
      payload = request.get_data(as_text=True)
      if not verify_signature(payload, request.headers.get('X-Firma-Signature'), WEBHOOK_SECRET):
          return jsonify({'error': 'Invalid signature'}), 401

      event = request.get_json()
      if event['type'] != 'signing_request.completed':
          return jsonify({'received': True}), 200

      signing_request_id = event['data']['signing_request']['id']

      original = db.pending_chains.find_one({'signing_request_id': signing_request_id})
      if not original:
          return jsonify({'received': True}), 200

      fields_resp = requests.get(
          f"{API_BASE}/signing-requests/{signing_request_id}/fields",
          headers={'Authorization': f"Bearer {API_KEY}"}
      )
      fields = fields_resp.json()['results']
      next_signer_field = next((f for f in fields if f.get('variable_name') == 'next_signer_email'), None)
      next_signer_email = next_signer_field.get('final_value') if next_signer_field else None

      if not next_signer_email:
          return jsonify({'received': True}), 200

      create_resp = requests.post(
          f"{API_BASE}/signing-requests/create-and-send",
          headers={'Authorization': f"Bearer {API_KEY}", 'Content-Type': 'application/json'},
          json={
              'name': f"{event['data']['signing_request']['name']} - Second Signer",
              'template_id': original['template_id'],
              'recipients': [{
                  'first_name': 'Next',
                  'last_name': 'Signer',
                  'email': next_signer_email,
                  'designation': 'Signer',
                  'order': 1
              }]
          }
      )
      created = create_resp.json()

      db.pending_chains.update_one(
          {'signing_request_id': signing_request_id},
          {'$set': {'chained_signing_request_id': created['id']}}
      )

      return jsonify({'received': True}), 200

  def verify_signature(payload, signature_header, secret):
      if not signature_header:
          return False
      parts = dict(p.split('=') for p in signature_header.split(','))
      if 't' not in parts or 'v1' not in parts:
          return False
      signed_payload = f"{parts['t']}.{payload}"
      expected = hmac.new(secret.encode(), signed_payload.encode(), hashlib.sha256).hexdigest()
      return hmac.compare_digest(parts['v1'], expected)

  if __name__ == '__main__':
      app.run(port=3000)
  ```
</CodeGroup>

<Tip>
  Responde `200` antes de hacer la consulta del campo y la llamada de creación y envío posterior — la entrega de webhooks de Firma tiene un tiempo de espera de 5 segundos, y el patrón anterior implica dos llamadas salientes a la API por sí solo.
</Tip>

### Enfoque 2: Firma incrustada con campos de identidad editables

Usa este enfoque cuando incrustas la firma directamente en tu aplicación y quieres que el firmante 2 confirme o corrija su propia identidad al abrir la vista de firma — sin necesidad de un flujo basado en correo electrónico.

<Steps>
  <Step title="Crea y envía la solicitud n.º 1 para el firmante 1">
    Incluye un campo de texto (p. ej. `variable_name: "next_signer_email"`) para que el firmante 1 proporcione el correo del firmante 2. Establece `send_signing_email: false`, ya que tú mismo incrustarás la vista de firma.
  </Step>

  <Step title="Incrusta la vista de firma del firmante 1">
    Usa el [componente de firma incrustable](/guides/embeddable-signing) para renderizar la vista de firma del firmante 1 en tu aplicación. Escucha el evento postMessage `firma:signing:completed`.
  </Step>

  <Step title="Al completarse, lee el correo del firmante 2 desde el campo">
    Llama a `GET /signing-requests/{id}/fields` y lee `final_value` del campo con `variable_name: "next_signer_email"`.
  </Step>

  <Step title="Crea la solicitud n.º 2 con identity_editable_fields">
    Crea una nueva solicitud de firma para el firmante 2 con `settings.identity_editable_fields` establecido en `["first_name", "last_name", "email"]`. Esto permite que el firmante 2 revise y corrija su propia identidad al abrir la vista de firma — útil cuando el firmante 1 pudo haber proporcionado datos aproximados.
  </Step>

  <Step title="Incrusta la vista de firma del firmante 2">
    Renderiza la vista de firma del firmante 2 en tu aplicación. El firmante 2 ve su identidad precargada, puede corregirla si es necesario, y firma.
  </Step>
</Steps>

<CodeGroup>
  ```js Frontend (embed + postMessage listener) theme={null}
  // Step 1: Your backend creates the signing request and returns the recipient's signing URL
  const { signingUrl, signingRequestId } = await fetch('/api/create-dynamic-signer-request', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ documentId, signer1Email, signer1Name })
  }).then(r => r.json())

  // Step 2: Embed signer 1's signing view
  const iframe = document.createElement('iframe')
  iframe.src = signingUrl
  iframe.style.width = '100%'
  iframe.style.height = '900px'
  iframe.frameBorder = '0'
  iframe.allow = 'camera;microphone;clipboard-write'
  document.getElementById('signing-container').appendChild(iframe)

  // Step 3: Listen for completion
  window.addEventListener('message', async (event) => {
    if (event.data?.type !== 'firma:signing:completed') return

    // Step 4: Your backend reads the field, creates request #2, returns signer 2's signing URL
    const { signingUrl: signer2Url } = await fetch('/api/chain-dynamic-signer', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ originalSigningRequestId: signingRequestId })
    }).then(r => r.json())

    // Step 5: Embed signer 2's signing view
    iframe.src = signer2Url
  })
  ```

  ```js Backend (Node.js / Express) theme={null}
  import express from 'express'

  const app = express()
  app.use(express.json())

  const API_KEY = process.env.FIRMA_API_KEY
  const API_BASE = 'https://api.firma.dev/functions/v1/signing-request-api'

  // Step 1: Create request #1 for signer 1
  app.post('/api/create-dynamic-signer-request', async (req, res) => {
    const { documentId, signer1Email, signer1Name } = req.body

    const resp = await fetch(`${API_BASE}/signing-requests/create-and-send`, {
      method: 'POST',
      headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        name: 'Contract — Dynamic Signer',
        document_id: documentId,
        settings: { send_signing_email: false },
        recipients: [{
          first_name: signer1Name.split(' ')[0],
          last_name: signer1Name.split(' ').slice(1).join(' ') || signer1Name,
          email: signer1Email,
          designation: 'Signer',
          order: 1
        }],
        fields: [{
          type: 'text',
          variable_name: 'next_signer_email',
          required: true,
          x: 50, y: 700, width: 200, height: 30, page: 0,
          recipient_order: 1
        }]
      })
    })
    const data = await resp.json()
    const recipient = data.recipients[0]
    const signingUrl = `https://app.firma.dev/signing/${recipient.id}`

    res.json({ signingUrl, signingRequestId: data.id })
  })

  // Steps 3–4: Read signer 2's email, create request #2 with editable identity
  app.post('/api/chain-dynamic-signer', async (req, res) => {
    const { originalSigningRequestId } = req.body

    // Read signer 1's field values
    const fieldsResp = await fetch(
      `${API_BASE}/signing-requests/${originalSigningRequestId}/fields`,
      { headers: { Authorization: `Bearer ${API_KEY}` } }
    )
    const { results: fields } = await fieldsResp.json()
    const nextEmail = fields.find(f => f.variable_name === 'next_signer_email')?.final_value

    if (!nextEmail) return res.status(400).json({ error: 'Signer 1 did not provide next signer email' })

    // Create request #2 with identity_editable_fields
    const createResp = await fetch(`${API_BASE}/signing-requests/create-and-send`, {
      method: 'POST',
      headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        name: 'Contract — Second Signer',
        document_id: req.body.documentId || null,
        template_id: req.body.templateId || null,
        settings: {
          send_signing_email: false,
          identity_editable_fields: ['first_name', 'last_name', 'email'],
          notify_identity_change_email: 1
        },
        recipients: [{
          first_name: 'Signer',
          last_name: '2',
          email: nextEmail,
          designation: 'Signer',
          order: 1
        }]
      })
    })
    const created = await createResp.json()
    const signingUrl = `https://app.firma.dev/signing/${created.recipients[0].id}`

    // Store the link in your own database
    await db.signingChains.insert({
      original_id: originalSigningRequestId,
      chained_id: created.id,
      created_at: new Date()
    })

    res.json({ signingUrl })
  })

  app.listen(3000)
  ```
</CodeGroup>

<Note>
  Establecer `identity_editable_fields: ["first_name", "last_name", "email"]` permite que el firmante 2 actualice su propio nombre y correo en la vista de firma antes de firmar. Si el firmante 1 proporcionó un nombre aproximado, el firmante 2 lo corrige por sí mismo. Establece `notify_identity_change_email: 1` para recibir una notificación cuando un firmante cambie su identidad.
</Note>

## Patrón: Firma en paralelo

Usa este patrón cuando los firmantes son independientes entre sí — nadie necesita esperar a que otro termine.

Establece `settings.use_signing_order: false` en la solicitud. Con esta opción desactivada, Firma envía un correo a **todos** los destinatarios en el momento del envío, en lugar de condicionar los niveles posteriores a que terminen los anteriores. Los valores de `order` se siguen almacenando en cada destinatario, pero no se aplican — nadie recibe un bloqueo por firmar fuera de turno.

```json theme={null}
{
  "name": "Board Resolution",
  "template_id": "tmpl_123",
  "settings": {
    "use_signing_order": false
  },
  "recipients": [
    { "first_name": "Alice", "last_name": "Johnson", "email": "alice@example.com", "designation": "Signer", "order": 1 },
    { "first_name": "Bob", "last_name": "Smith", "email": "bob@example.com", "designation": "Signer", "order": 2 },
    { "first_name": "Carol", "last_name": "Lee", "email": "carol@example.com", "designation": "Signer", "order": 3 }
  ]
}
```

Los tres reciben su correo de firma de inmediato. La solicitud se completa (y se dispara `signing_request.completed`) una vez que todos han terminado, sin importar el orden en que realmente firmen.

## Patrón: Firmante y aprobador

El modelo de designación de Firma es deliberadamente excluyente: una fila de destinatario es `Signer`, `Approver` o `CC` — nunca más de uno. Si la misma persona necesita firmar y luego aprobar, aparece como **dos filas** con distintos valores de `order`, no como una fila con dos roles.

```json theme={null}
{
  "name": "Expense Report",
  "template_id": "tmpl_123",
  "recipients": [
    {
      "first_name": "Alice",
      "last_name": "Johnson",
      "email": "alice@example.com",
      "designation": "Signer",
      "order": 1
    },
    {
      "first_name": "Alice",
      "last_name": "Johnson",
      "email": "alice@example.com",
      "designation": "Approver",
      "order": 2
    }
  ]
}
```

<Note>
  Los campos también importan aquí: los campos `approval_signature`, `approval_checkmark` y `approval_date` solo se pueden asignar a un destinatario cuya `designation` sea `Approver` — asignar uno a una fila `Signer` devuelve un `400`. Su valor se genera del lado del servidor cuando el aprobador completa su revisión; tú no lo envías.
</Note>

Esto también se combina con la firma secuencial: dale a la fila `Signer` un `order` menor que a la fila `Approver`, y el propio paso de aprobación de Alice no se desbloqueará hasta que ella termine de firmar.

## Manejo de errores: 409 `ALREADY_SENT`

`ALREADY_SENT` significa que la solicitud de firma tiene una marca de tiempo `sent_on` y que la operación que intentaste solo funciona en un borrador. Se devuelve, con código `409`, desde:

| Endpoint                        | Motivo                                                                                                                                                                      |
| :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PATCH /signing-requests/{id}`  | La solicitud ya fue enviada — no se permite actualizar ninguna propiedad, destinatario ni campo                                                                             |
| `PUT /signing-requests/{id}`    | Lo mismo — la actualización completa también requiere `not_sent`                                                                                                            |
| `DELETE /signing-requests/{id}` | Las solicitudes enviadas no se pueden eliminar; el mensaje de error te remite a [`/cancel`](/api-reference/v01.33.00/signing-requests/cancel-a-signing-request) en su lugar |

Lo que *sí* puedes seguir haciendo con una solicitud enviada pero no finalizada:

* [`POST /signing-requests/{id}/resend`](/api-reference/v01.33.00/signing-requests/resend-signing-request-to-specific-recipients) — reenvía el correo de notificación a los destinatarios que se encuentran actualmente en el nivel de firma activo y aún no han terminado. No te permite cambiar su correo ni ningún otro dato del destinatario.
* [`POST /signing-requests/{id}/cancel`](/api-reference/v01.33.00/signing-requests/cancel-a-signing-request) — detiene toda la solicitud para todos.

Si te encuentras con `ALREADY_SENT` al intentar corregir un correo de destinatario mal escrito o agregar un destinatario que olvidaste, no hay una corrección in situ — cancela y vuelve a crear, o (para el caso de "aún no conocía al segundo destinatario") usa el patrón de [segundo firmante dinámico](#pattern-dynamic-second-signer) descrito antes.

## Próximos pasos

* [Envío de una solicitud de firma](/guides/sending-signing-request) — el flujo básico de creación y envío sobre el que se construyen estos patrones
* [Webhooks](/guides/webhooks) — tipos de eventos, verificación de firma y comportamiento de reintentos
* [Precarga de campos](/guides/field-prefilling) — completa campos con datos conocidos en lugar de pedirle al firmante que los complete
