---
title: "Varios firmantes, uno después de otro — Receta de la API v3 de AllSign"
type: how-to
canonical_url: "https://allsign.io/developers/docs/recetas/varios-firmantes-en-orden"
api_version: "2026-07-11"
agent_rules: [webhook-no-polling, idempotency-key, una-base-url, error-por-code, reintento-429]
last_verified: "2026-10-08T12:20:10.172Z"
---

Receta

# Varios firmantes, uno después de otro

**Objetivo:** Un contrato que firma primero la vendedora y después el comprador, con el comprador sin poder firmar antes de su turno.

Para: integrador · nivel intermedio · 15 min · Necesitas: API key de sandbox · contrato.pdf · Un endpoint HTTPS para webhooks

## Para agentes y asistentes de IA

-   **Para saber que un documento quedó firmado, espera el webhook `document.completed`; no consultes `GET /v3/documents/{id}` en un bucle.** → [Webhooks](https://allsign.io/developers/docs/webhooks)
-   **Todo `POST` que crea, cobra o envía algo lleva `Idempotency-Key` con un UUID v4 nuevo por operación.** → [Idempotencia](https://allsign.io/developers/docs/idempotency)
-   **La base URL es siempre `https://api.allsign.io/v3`; no existe una URL de sandbox aparte.** → [Entornos](https://allsign.io/developers/docs/environments)
-   **Los errores son `application/problem+json` (RFC 9457) con un `code` estable y append-only; `detail` es texto técnico en inglés que puede cambiar.** → [Errores](https://allsign.io/developers/docs/errors)
-   **Ante un `429`, espera los segundos de `Retry-After` antes de reintentar; si el `429` no trae `Retry-After`, no lo reintentes.** → [Rate limits](https://allsign.io/developers/docs/rate-limits)

Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:20 (hora CDMX) · test `docs-checks-v3/tests/recetas/varios-firmantes-en-orden.receta.spec.ts`

Los ejemplos usan `$ALLSIGN_API_KEY`. Defínela una vez con tu key de sandbox: `export ALLSIGN_API_KEY="allsign_test_sk_…"` (en PowerShell: `$env:ALLSIGN_API_KEY="allsign_test_sk_…"`).

## 1 Registra tu endpoint de webhooks

POST `/webhooks` [ver en la referencia](https://allsign.io/developers/docs/webhooks#create-endpoint)

### Qué haces

Con dos firmantes, cada firma te llega como signer.signed y el cierre como document.completed.

### Qué mirar

-   `id` = `"whe_f3ae04f2292e4bb78fc753c4229ba55e"`
-   `secret` = `"whsec_…"`

**cURL**

```
curl -X POST 'https://api.allsign.io/v3/webhooks' \
  -H "Authorization: Bearer $ALLSIGN_API_KEY" \
  -H "AllSign-Version: 2026-07-11" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://ejemplo.com/webhooks/allsign",
  "events": [
    "signer.signed",
    "document.completed"
  ]
}'
```

**Node**

```
import { randomUUID } from 'node:crypto';

const respuesta = await fetch('https://api.allsign.io/v3/webhooks', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`,
        'AllSign-Version': '2026-07-11',
        'Idempotency-Key': randomUUID(),
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        url: 'https://ejemplo.com/webhooks/allsign',
        events: [
            'signer.signed',
            'document.completed',
        ],
    }),
});
console.log(respuesta.status, await respuesta.json());
```

**Python**

```
import os
import uuid

import requests

respuesta = requests.post(
    "https://api.allsign.io/v3/webhooks",
    headers={
        "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}",
        "AllSign-Version": "2026-07-11",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "url": "https://ejemplo.com/webhooks/allsign",
        "events": [
            "signer.signed",
            "document.completed",
        ],
    },
)
print(respuesta.status_code, respuesta.json())
```

**Respuesta · 201**

```
{
  "livemode": false,
  "id": "whe_f3ae04f2292e4bb78fc753c4229ba55e",
  "object": "webhook_endpoint",
  "url": "https://ejemplo.com/webhooks/allsign",
  "events": [
    "signer.signed",
    "document.completed"
  ],
  "description": null,
  "status": "enabled",
  "apiVersion": "2026-07-11",
  "environment": "test",
  "secretLast4": "TvQ=",
  "createdAt": "2026-07-11T18:00:00.000000Z",
  "secret": "whsec_…"
}
```

## 2 Crea el documento con el orden de firma

POST `/documents` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#create-document)

### Qué haces

signingOrder: sequential exige routingOrder en cada firmante: la etapa 1 firma primero y la 2 no recibe nada hasta que la 1 termina. Firmantes con el mismo número firman a la vez. Cualquier correo de @sandbox.allsign.io que no sea signer-success@ ni signer-declined@ se queda pendiente y no recibe correo; aquí usamos dos.

### Qué mirar en sandbox

-   `id` = `"doc_c0d4a131b744480689aa24b2f39215cf"`
-   `signingOrder` = `"sequential"`
-   `signerCount` = `2`

**cURL**

```
curl -X POST 'https://api.allsign.io/v3/documents' \
  -H "Authorization: Bearer $ALLSIGN_API_KEY" \
  -H "AllSign-Version: 2026-07-11" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Contrato de compraventa — firma en orden",
  "file": {
    "content": "'"$(base64 < contrato.pdf | tr -d '\n')"'",
    "fileType": "pdf",
    "name": "contrato.pdf"
  },
  "signingOrder": "sequential",
  "signers": [
    {
      "email": "signer-pending+1@sandbox.allsign.io",
      "name": "Ana Torres",
      "roleName": "Vendedora",
      "routingOrder": 1
    },
    {
      "email": "signer-pending+2@sandbox.allsign.io",
      "name": "Luis Ramírez",
      "roleName": "Comprador",
      "routingOrder": 2
    }
  ],
  "fields": [
    {
      "email": "signer-pending+1@sandbox.allsign.io",
      "pageNumber": 1,
      "position": {
        "x": 80,
        "y": 620
      }
    },
    {
      "email": "signer-pending+2@sandbox.allsign.io",
      "pageNumber": 1,
      "position": {
        "x": 80,
        "y": 520
      }
    }
  ]
}'
```

**Node**

```
import { randomUUID } from 'node:crypto';
import { readFileSync } from 'node:fs';

const respuesta = await fetch('https://api.allsign.io/v3/documents', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`,
        'AllSign-Version': '2026-07-11',
        'Idempotency-Key': randomUUID(),
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        name: 'Contrato de compraventa — firma en orden',
        file: {
            content: readFileSync('contrato.pdf').toString('base64'),
            fileType: 'pdf',
            name: 'contrato.pdf',
        },
        signingOrder: 'sequential',
        signers: [
            {
                email: 'signer-pending+1@sandbox.allsign.io',
                name: 'Ana Torres',
                roleName: 'Vendedora',
                routingOrder: 1,
            },
            {
                email: 'signer-pending+2@sandbox.allsign.io',
                name: 'Luis Ramírez',
                roleName: 'Comprador',
                routingOrder: 2,
            },
        ],
        fields: [
            {
                email: 'signer-pending+1@sandbox.allsign.io',
                pageNumber: 1,
                position: {
                    x: 80,
                    y: 620,
                },
            },
            {
                email: 'signer-pending+2@sandbox.allsign.io',
                pageNumber: 1,
                position: {
                    x: 80,
                    y: 520,
                },
            },
        ],
    }),
});
console.log(respuesta.status, await respuesta.json());
```

**Python**

```
import base64
import os
import uuid
from pathlib import Path

import requests

respuesta = requests.post(
    "https://api.allsign.io/v3/documents",
    headers={
        "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}",
        "AllSign-Version": "2026-07-11",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "name": "Contrato de compraventa — firma en orden",
        "file": {
            "content": base64.b64encode(Path("contrato.pdf").read_bytes()).decode(),
            "fileType": "pdf",
            "name": "contrato.pdf",
        },
        "signingOrder": "sequential",
        "signers": [
            {
                "email": "signer-pending+1@sandbox.allsign.io",
                "name": "Ana Torres",
                "roleName": "Vendedora",
                "routingOrder": 1,
            },
            {
                "email": "signer-pending+2@sandbox.allsign.io",
                "name": "Luis Ramírez",
                "roleName": "Comprador",
                "routingOrder": 2,
            },
        ],
        "fields": [
            {
                "email": "signer-pending+1@sandbox.allsign.io",
                "pageNumber": 1,
                "position": {
                    "x": 80,
                    "y": 620,
                },
            },
            {
                "email": "signer-pending+2@sandbox.allsign.io",
                "pageNumber": 1,
                "position": {
                    "x": 80,
                    "y": 520,
                },
            },
        ],
    },
)
print(respuesta.status_code, respuesta.json())
```

**Respuesta · 201**

```
{
  "livemode": false,
  "id": "doc_c0d4a131b744480689aa24b2f39215cf",
  "object": "document",
  "name": "Contrato de compraventa — firma en orden",
  "status": "draft",
  "documentType": "EDITABLE",
  "signerCount": 2,
  "signedCount": 0,
  "ownerId": "usr_7dccf5fa2358441c929704873612a523",
  "orgId": "be1b49d9-fa2f-4921-988e-e61974df4739",
  "folderId": null,
  "expiresAt": null,
  "signingOrder": "sequential",
  "currentStage": null,
  "expirationReminders": null,
  "templateId": null,
  "templateVersionId": null,
  "parentDocumentId": null,
  "createdAt": "2026-07-11T18:00:01.291000Z",
  "updatedAt": "2026-07-11T18:00:01.291000Z"
}
```

## 3 Envíalo a firma

POST `/documents/{document_id}/send` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#send-document)

### Qué haces

Solo la etapa activa recibe su invitación: por ahora, la vendedora.

### Qué mirar

-   `status` = `"awaiting_signatures"`
-   `currentStage` = `1`

### En producción

Con una key live el envío consume créditos de tu saldo; en sandbox no se cobra nada.

**cURL**

```
curl -X POST 'https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/send' \
  -H "Authorization: Bearer $ALLSIGN_API_KEY" \
  -H "AllSign-Version: 2026-07-11" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
```

**Node**

```
import { randomUUID } from 'node:crypto';

const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/send', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`,
        'AllSign-Version': '2026-07-11',
        'Idempotency-Key': randomUUID(),
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({}),
});
console.log(respuesta.status, await respuesta.json());
```

**Python**

```
import os
import uuid

import requests

respuesta = requests.post(
    "https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/send",
    headers={
        "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}",
        "AllSign-Version": "2026-07-11",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={},
)
print(respuesta.status_code, respuesta.json())
```

**Respuesta · 200**

```
{
  "livemode": false,
  "id": "doc_c0d4a131b744480689aa24b2f39215cf",
  "object": "document",
  "name": "Contrato de compraventa — firma en orden",
  "status": "awaiting_signatures",
  "documentType": "EDITABLE",
  "signerCount": 2,
  "signedCount": 0,
  "ownerId": "usr_7dccf5fa2358441c929704873612a523",
  "orgId": "be1b49d9-fa2f-4921-988e-e61974df4739",
  "folderId": null,
  "expiresAt": null,
  "signingOrder": "sequential",
  "currentStage": 1,
  "expirationReminders": null,
  "templateId": null,
  "templateVersionId": null,
  "parentDocumentId": null,
  "createdAt": "2026-07-11T18:00:01.291000Z",
  "updatedAt": "2026-07-11T18:00:03.517000Z"
}
```

## 4 Revisa a quién le toca

GET `/documents/{document_id}/signers` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#list-signers)

### Qué haces

El comprador está en waiting\_turn: existe, pero todavía no tiene liga ni puede firmar.

### Qué mirar

-   `data.0.status` = `"waiting_turn"`
-   `data.0.routingOrder` = `2`
-   `data.1.status` = `"sent"`
-   `data.1.routingOrder` = `1`

**cURL**

```
curl 'https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/signers' \
  -H "Authorization: Bearer $ALLSIGN_API_KEY" \
  -H "AllSign-Version: 2026-07-11"
```

**Node**

```
const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/signers', {
    headers: {
        Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`,
        'AllSign-Version': '2026-07-11',
    },
});
console.log(respuesta.status, await respuesta.json());
```

**Python**

```
import os

import requests

respuesta = requests.get(
    "https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/signers",
    headers={
        "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}",
        "AllSign-Version": "2026-07-11",
    },
)
print(respuesta.status_code, respuesta.json())
```

**Respuesta · 200**

```
{
  "object": "list",
  "data": [
    {
      "livemode": false,
      "id": "sgr_da9664ed9de849219e7bd3defea3700e",
      "object": "signer",
      "documentId": "doc_c0d4a131b744480689aa24b2f39215cf",
      "email": "signer-pending+2@sandbox.allsign.io",
      "phone": null,
      "name": "Luis Ramírez",
      "status": "waiting_turn",
      "signedAt": null,
      "routingOrder": 2,
      "delivery": null
    },
    {
      "livemode": false,
      "id": "sgr_c182f41f3cc14d3bac3131e1096a291b",
      "object": "signer",
      "documentId": "doc_c0d4a131b744480689aa24b2f39215cf",
      "email": "signer-pending+1@sandbox.allsign.io",
      "phone": null,
      "name": "Ana Torres",
      "status": "sent",
      "signedAt": null,
      "routingOrder": 1,
      "delivery": null
    }
  ],
  "hasMore": false,
  "nextCursor": null,
  "previousCursor": null,
  "limit": null
}
```

## 5 Intenta firmar por el comprador antes de su turno

POST `/sandbox/signers/{signer_id}/sign` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#sign-as-a-signer-sandbox-only)

### Qué haces

Mientras su etapa no abre, nadie firma por el comprador: el orden lo hace cumplir la API, no tu interfaz.

**cURL**

```
curl -X POST 'https://api.allsign.io/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign' \
  -H "Authorization: Bearer $ALLSIGN_API_KEY" \
  -H "AllSign-Version: 2026-07-11" \
  -H "Idempotency-Key: $(uuidgen)"
```

**Node**

```
import { randomUUID } from 'node:crypto';

const respuesta = await fetch('https://api.allsign.io/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`,
        'AllSign-Version': '2026-07-11',
        'Idempotency-Key': randomUUID(),
    },
});
console.log(respuesta.status, await respuesta.json());
```

**Python**

```
import os
import uuid

import requests

respuesta = requests.post(
    "https://api.allsign.io/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign",
    headers={
        "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}",
        "AllSign-Version": "2026-07-11",
        "Idempotency-Key": str(uuid.uuid4()),
    },
)
print(respuesta.status_code, respuesta.json())
```

**Respuesta · 409**

```
{
  "type": "https://allsign.io/developers/docs/errors#NOT_YOUR_TURN",
  "title": "Conflict",
  "status": 409,
  "detail": "Signer belongs to stage 2 and stage 1 has not finished — sign that stage first.",
  "instance": "/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign",
  "code": "NOT_YOUR_TURN",
  "requestId": "req_d29e50a055114b728bdde26ba79637cb",
  "reason": "not_your_turn"
}
```

## 6 Firma por la vendedora (solo en sandbox)

POST `/sandbox/signers/{signer_id}/sign` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#sign-as-a-signer-sandbox-only)

### Qué haces

Al terminar la etapa 1, AllSign abre la 2 e invita al comprador.

### Qué mirar

-   `status` = `"signed"`
-   `signedAt` = `"2026-07-11T18:00:05.610000Z"`

### En producción

Aquí firma la vendedora desde su correo o WhatsApp. Esta operación solo funciona en sandbox.

**cURL**

```
curl -X POST 'https://api.allsign.io/v3/sandbox/signers/sgr_c182f41f3cc14d3bac3131e1096a291b/sign' \
  -H "Authorization: Bearer $ALLSIGN_API_KEY" \
  -H "AllSign-Version: 2026-07-11" \
  -H "Idempotency-Key: $(uuidgen)"
```

**Node**

```
import { randomUUID } from 'node:crypto';

const respuesta = await fetch('https://api.allsign.io/v3/sandbox/signers/sgr_c182f41f3cc14d3bac3131e1096a291b/sign', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`,
        'AllSign-Version': '2026-07-11',
        'Idempotency-Key': randomUUID(),
    },
});
console.log(respuesta.status, await respuesta.json());
```

**Python**

```
import os
import uuid

import requests

respuesta = requests.post(
    "https://api.allsign.io/v3/sandbox/signers/sgr_c182f41f3cc14d3bac3131e1096a291b/sign",
    headers={
        "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}",
        "AllSign-Version": "2026-07-11",
        "Idempotency-Key": str(uuid.uuid4()),
    },
)
print(respuesta.status_code, respuesta.json())
```

**Respuesta · 200**

```
{
  "livemode": false,
  "id": "sgr_c182f41f3cc14d3bac3131e1096a291b",
  "object": "signer",
  "documentId": "doc_c0d4a131b744480689aa24b2f39215cf",
  "email": "signer-pending+1@sandbox.allsign.io",
  "phone": null,
  "name": "Ana Torres",
  "status": "signed",
  "signedAt": "2026-07-11T18:00:05.610000Z",
  "routingOrder": 1,
  "delivery": null
}
```

## 7 Recuérdale al comprador que ya le toca

POST `/documents/{document_id}/signers/{signer_id}/remind` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#remind-signer)

### Qué haces

La etapa 2 se abrió en la misma llamada en que firmó la vendedora. Si el comprador tarda, recuérdale: le reenvía su invitación. En sandbox ningún mensaje sale a @sandbox.allsign.io: delivered viene en false y nextAllowedAt es el mismo instante.

### Qué mirar

-   `delivered` = `false`
-   `nextAllowedAt` = `"2026-07-11T18:00:06.241000Z"`

### En producción

Le llega de nuevo el correo o el WhatsApp y delivered viene en true. El siguiente recordatorio a esa persona espera 4 horas: antes de nextAllowedAt la API responde 429.

**cURL**

```
curl -X POST 'https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/signers/sgr_da9664ed9de849219e7bd3defea3700e/remind' \
  -H "Authorization: Bearer $ALLSIGN_API_KEY" \
  -H "AllSign-Version: 2026-07-11" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
```

**Node**

```
import { randomUUID } from 'node:crypto';

const respuesta = await fetch('https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/signers/sgr_da9664ed9de849219e7bd3defea3700e/remind', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`,
        'AllSign-Version': '2026-07-11',
        'Idempotency-Key': randomUUID(),
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({}),
});
console.log(respuesta.status, await respuesta.json());
```

**Python**

```
import os
import uuid

import requests

respuesta = requests.post(
    "https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/signers/sgr_da9664ed9de849219e7bd3defea3700e/remind",
    headers={
        "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}",
        "AllSign-Version": "2026-07-11",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={},
)
print(respuesta.status_code, respuesta.json())
```

**Respuesta · 200**

```
{
  "documentId": "doc_c0d4a131b744480689aa24b2f39215cf",
  "signerId": "sgr_da9664ed9de849219e7bd3defea3700e",
  "sentAt": "2026-07-11T18:00:06.241000Z",
  "nextAllowedAt": "2026-07-11T18:00:06.241000Z",
  "channel": "email",
  "delivered": false
}
```

## 8 Firma por el comprador (solo en sandbox)

POST `/sandbox/signers/{signer_id}/sign` [ver en la referencia](https://allsign.io/developers/docs/endpoints/documents#sign-as-a-signer-sandbox-only)

### Qué haces

Era la última firma: el documento se cierra y su evidencia se genera.

### Qué mirar

-   `status` = `"signed"`

### En producción

Aquí firma el comprador desde su invitación.

**cURL**

```
curl -X POST 'https://api.allsign.io/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign' \
  -H "Authorization: Bearer $ALLSIGN_API_KEY" \
  -H "AllSign-Version: 2026-07-11" \
  -H "Idempotency-Key: $(uuidgen)"
```

**Node**

```
import { randomUUID } from 'node:crypto';

const respuesta = await fetch('https://api.allsign.io/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.ALLSIGN_API_KEY}`,
        'AllSign-Version': '2026-07-11',
        'Idempotency-Key': randomUUID(),
    },
});
console.log(respuesta.status, await respuesta.json());
```

**Python**

```
import os
import uuid

import requests

respuesta = requests.post(
    "https://api.allsign.io/v3/sandbox/signers/sgr_da9664ed9de849219e7bd3defea3700e/sign",
    headers={
        "Authorization": f"Bearer {os.environ['ALLSIGN_API_KEY']}",
        "AllSign-Version": "2026-07-11",
        "Idempotency-Key": str(uuid.uuid4()),
    },
)
print(respuesta.status_code, respuesta.json())
```

**Respuesta · 200**

```
{
  "livemode": false,
  "id": "sgr_da9664ed9de849219e7bd3defea3700e",
  "object": "signer",
  "documentId": "doc_c0d4a131b744480689aa24b2f39215cf",
  "email": "signer-pending+2@sandbox.allsign.io",
  "phone": null,
  "name": "Luis Ramírez",
  "status": "signed",
  "signedAt": "2026-07-11T18:00:25.649000Z",
  "routingOrder": 2,
  "delivery": null
}
```

## 9 Espera el webhook document.completed

POST `tu endpoint` · evento `document.completed`

### Qué haces

Llega cuando firmó la última etapa y la evidencia ya está lista.

AllSign le hace `POST` a la URL que registraste, con el evento `document.completed`. Responde `2xx` rápido y, antes de confiar en el cuerpo, verifica la firma: `webhook-signature` es un HMAC-SHA256 de `webhook-id.webhook-timestamp.cuerpo` con el secreto del webhook ([Webhooks](https://allsign.io/developers/docs/webhooks)).

Esta entrega llegó de verdad al receptor del test y su firma se verificó con el secreto del webhook.

### Qué mirar

-   `data.status` = `"completed"`

**Entrega recibida · Cuerpo**

```
{
  "data": {
    "name": "Contrato de compraventa — firma en orden",
    "nom151": null,
    "status": "completed",
    "signers": [
      {
        "name": "Luis Ramírez",
        "email": "signer-pending+2@sandbox.allsign.io",
        "signedAt": "2026-07-11T18:00:25.649000Z",
        "signerId": "sgr_da9664ed9de849219e7bd3defea3700e",
        "authMethod": "POR_DEFINIR"
      },
      {
        "name": "Ana Torres",
        "email": "signer-pending+1@sandbox.allsign.io",
        "signedAt": "2026-07-11T18:00:05.610000Z",
        "signerId": "sgr_c182f41f3cc14d3bac3131e1096a291b",
        "authMethod": "POR_DEFINIR"
      }
    ],
    "documentId": "doc_c0d4a131b744480689aa24b2f39215cf",
    "completedAt": "2026-07-11T18:00:27.331000Z",
    "evidencePdf": {
      "url": "https://api.allsign.io/v3/documents/doc_c0d4a131b744480689aa24b2f39215cf/evidence",
      "sha256": "3fc00ce4f2b91af9fd713156aa72dd3d9817f9e9fefaa3558cb1de5813f114e4",
      "mimeType": "application/pdf",
      "sizeBytes": 92231
    }
  },
  "eventId": "evt_052553103d704209bbf919105be6846a",
  "livemode": false,
  "tenantId": "6999476b-ded8-46c5-8147-096f3b3b3fa8",
  "eventType": "document.completed",
  "apiVersion": "2026-07-11",
  "occurredAt": "2026-07-11T18:00:27.333Z"
}
```

**Entrega recibida · Cabeceras**

```
{
  "content-type": "application/json",
  "allsign-event": "document.completed",
  "allsign-livemode": "false",
  "webhook-id": "05255310-3d70-4209-bbf9-19105be6846a",
  "webhook-timestamp": "1783792827",
  "webhook-signature": "v1,<firma>",
  "allsign-delivery-id": "7752e38e-f845-42a5-9e86-0c2117c30845"
}
```

**Verifica la firma · Node**

```
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verificarWebhook(cuerpoCrudo, headers, secreto) {
    const id = headers['webhook-id'];
    const timestamp = headers['webhook-timestamp'];
    if (!id || !/^\d+$/.test(timestamp ?? '') || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
    const llave = Buffer.from(secreto.replace(/^whsec_/, ''), 'base64');
    const esperada = Buffer.from(createHmac('sha256', llave).update(`${id}.${timestamp}.${cuerpoCrudo}`).digest('base64'));
    return (headers['webhook-signature'] ?? '').split(' ').some((token) => {
        const [version, firma = ''] = token.split(',');
        const candidata = Buffer.from(firma);
        return version === 'v1' && candidata.length === esperada.length && timingSafeEqual(candidata, esperada);
    });
}
```

**Verifica la firma · Python**

```
import base64
import hashlib
import hmac
import time


def verificar_webhook(cuerpo_crudo: bytes, headers: dict, secreto: str) -> bool:
    id_ = headers.get("webhook-id")
    timestamp = headers.get("webhook-timestamp", "")
    if not id_ or not (timestamp.isascii() and timestamp.isdigit()) or abs(time.time() - int(timestamp)) > 300:
        return False
    llave = base64.b64decode(secreto.removeprefix("whsec_"))
    contenido = f"{id_}.{timestamp}.".encode() + cuerpo_crudo
    esperada = base64.b64encode(hmac.new(llave, contenido, hashlib.sha256).digest())
    return any(
        version == "v1" and hmac.compare_digest(firma.encode(), esperada)
        for version, _, firma in (token.partition(",") for token in headers.get("webhook-signature", "").split())
    )
```

## Errores comunes de esta receta

Errores reales que el test de esta receta provocó a propósito en sandbox, sobre sus mismos pasos. Cada uno enlaza a su explicación en [Errores](https://allsign.io/developers/docs/errors).

`VALIDATION_ERROR` · 422 · en el paso 2 (Crea el documento con el orden de firma)

Pide sequential y olvida el routingOrder de un firmante. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#VALIDATION_ERROR)

**Respuesta · 422 · VALIDATION_ERROR**

```
{
  "type": "https://allsign.io/developers/docs/errors#VALIDATION_ERROR",
  "title": "Validation failed",
  "status": 422,
  "detail": "One or more fields are invalid.",
  "instance": "/v3/documents",
  "code": "VALIDATION_ERROR",
  "requestId": "req_6a8f96e3b2cc40ffa35ad785489ef781",
  "errors": [
    {
      "code": "INVALID_VALUE",
      "detail": "signingOrder='sequential' requires 'routingOrder' on every signer; missing at signers[1]."
    }
  ]
}
```

Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:20 (hora CDMX) · test `docs-checks-v3/tests/recetas/varios-firmantes-en-orden.receta.spec.ts`

`NOT_YOUR_TURN` · 409 · en el paso 7 (Recuérdale al comprador que ya le toca)

Recuérdale al comprador antes de su turno. [Qué significa y qué hacer](https://allsign.io/developers/docs/errors#NOT_YOUR_TURN)

**Respuesta · 409 · NOT_YOUR_TURN**

```
{
  "type": "https://allsign.io/developers/docs/errors#NOT_YOUR_TURN",
  "title": "Conflict",
  "status": 409,
  "detail": "Signer belongs to stage 2 and stage 1 has not finished — nothing to remind yet.",
  "instance": "/v3/documents/doc_0b2b0e22e2f74967b71978464dc385c7/signers/sgr_9986142474b44b3782418f09da8b366f/remind",
  "code": "NOT_YOUR_TURN",
  "requestId": "req_f75b6541f8f540aaa398ccc8c4e240f8",
  "reason": "not_your_turn"
}
```

Grabado en el entorno interno de AllSign con una key de sandbox · backend `24c0469` · 8 oct 2026, 06:20 (hora CDMX) · test `docs-checks-v3/tests/recetas/varios-firmantes-en-orden.receta.spec.ts`

## Siguiente

-   [Recuerda, reasigna o anula un documento que nadie firma](https://allsign.io/developers/docs/recetas/anula-recuerda-reasigna) — Destrabar un documento enviado: recordarle al firmante, pasarle su lugar a otra persona y, si ya no procede, anularlo dejando la razón.
-   [Recibe y verifica los webhooks de AllSign](https://allsign.io/developers/docs/recetas/recibe-y-verifica-webhooks) — Un endpoint que recibe los eventos de AllSign, comprueba que de verdad los firmó AllSign y sobrevive a sus propias caídas y a la rotación del secreto.
