Webhooks

Un webhook es un endpoint HTTPS tuyo que AllSign notifica con un POST firmado cada vez que algo relevante pasa en tu tenant —un documento se crea, se envía, se completa o se anula— sin que tengas que hacer polling. Todo endpoint v3 se crea firmado (HMAC obligatorio) y sellado con una versión de contrato con fecha.

¿Cómo funcionan?

  1. Registras una URL https:// de tu servidor con POST /v3/webhooks y eliges los eventos.
  2. AllSign te devuelve un secreto de firma (whsec_…) una sola vez. Guárdalo —no se vuelve a mostrar.
  3. Cuando ocurre un evento, AllSign hace un POST a tu URL con el sobre del evento en el body y las cabeceras de firma Standard Webhooks (webhook-id / webhook-timestamp / webhook-signature).
  4. Tu servidor verifica la firma, deduplica por eventId, procesa el evento y responde 2xx rápido.
Tu servidor ← POST (evento firmado) ← AllSign

Ids opacos con prefijo (whe_ endpoint, whd_ entrega, evt_ evento), camelCase en el wire, errores problem+json (RFC 9457) con code en UPPER_SNAKE, y las listas de endpoints y entregas paginan por cursor (startingAfter / endingBefore + hasMore). Cada respuesta trae headers RateLimit-*.

Scopes. Crear, editar y rotar exige webhook:write; leer (listar, consultar, entregas, catálogo) exige webhook:read; borrar exige webhook:delete. Una key sin el scope recibe 403 PERMISSION_DENIED con requiredScope.

Create endpoint

POST /v3/webhooks

Registra un endpoint firmado. Genera un secreto `whsec_` (**devuelto una sola vez**), fuerza HMAC, y estampa la versión de contrato con fecha (`apiVersion`) + el entorno de la key — no hay cruce `live`/`test`. Requiere `webhook:write`.

List endpoints

GET /v3/webhooks

Lista tus endpoints de webhook con paginación por cursor. **El secreto nunca aparece aquí** — solo `secretLast4`.

Retrieve endpoint

GET /v3/webhooks/{webhook_id}

Consulta un endpoint por su `id`. No incluye el secreto (solo `secretLast4`). Un `id` inexistente o de otro tenant responde **404 `WEBHOOK_NOT_FOUND`**.

Update endpoint

PATCH /v3/webhooks/{webhook_id}

Merge-patch: solo cambian los campos que envías. Puedes reasignar `url`, `events`, `description`, o pausar/reactivar con `disabled`. Enviar un campo desconocido o inmutable es un **422 `VALIDATION_ERROR`**. Requiere `webhook:write`.

Delete endpoint

DELETE /v3/webhooks/{webhook_id}

Elimina un endpoint. Requiere el scope `webhook:delete`. Las entregas en vuelo hacia ese endpoint se marcan como fallidas (`webhook deleted`).

Rotate secret

POST /v3/webhooks/{webhook_id}/rotate-secret

Acuña un secreto `whsec_` nuevo. El anterior se conserva como *secreto previo* durante una **ventana de 24 h** en la que el despachador firma con **ambos** — así rotas sin downtime. Requiere `webhook:write` y honra `Idempotency-Key` (un reintento con la misma llave reproduce el mismo secreto en vez de rotar dos veces).

List deliveries

GET /v3/webhooks/{webhook_id}/deliveries

El log de entregas de un endpoint — para depurar qué se envió, qué respondió tu servidor y cuántos intentos hubo. Pagina por cursor. Requiere `webhook:read`.

List events

GET /v3/webhooks/events

El catálogo **congelado** de eventos v3 a los que puedes suscribirte — la fuente de verdad contra la que valida `POST /v3/webhooks`. Requiere `webhook:read`.

El sobre del evento

Cada webhook v3 llega como un sobre camelCase con el payload específico dentro de data. No hay un campo event a nivel raíz (eso era un alias v2); el enrutamiento va por la cabecera AllSign-Event. Deduplica por eventId.

{
  "eventId": "evt_7c9e6679742540de944be07fc1f90ae7",
  "eventType": "document.completed",
  "apiVersion": "2026-07-11",
  "occurredAt": "2026-07-11T19:03:00.123Z",
  "tenantId": "550e8400-e29b-41d4-a716-446655440000",
  "livemode": true,
  "data": { }
}
CampoDescripción
eventIdID único del evento (evt_…). Úsalo para deduplicar —un reintento del productor reusa el mismo eventId. La cabecera webhook-id trae este mismo id, pero como UUID crudo (ver cabeceras).
eventTypeEl tipo de evento (ej. document.completed). Coincide con la cabecera AllSign-Event.
apiVersionVersión de contrato con fecha que congela la forma del data.
occurredAtCuándo ocurrió (ISO-8601 UTC con sufijo Z, precisión de milisegundos).
tenantIdTu tenant.
livemodetrue si el evento nació en entorno live.
dataEl payload específico del evento (ver el catálogo).

Cabeceras de cada entrega

AllSign firma con Standard Webhooks (standardwebhooks.com) —el estándar abierto que ya adoptaron OpenAI, Anthropic, Twilio y Supabase, entre otros. La ventaja práctica: puedes verificar con la librería oficial (standardwebhooks, disponible en 10+ lenguajes) en vez de escribir tu propio verificador.

CabeceraDescripción
webhook-idID estable del evento —el mismo en todos los reintentos de un mismo evento. Viaja como UUID crudo con guiones (ej. 7c9e6679-7425-40de-944b-e07fc1f90ae7), no con la forma evt_….
webhook-timestampUnix timestamp (segundos) con el que se firmó este intento. Un reintento trae uno nuevo.
webhook-signatureLa firma —ver Firma Standard Webhooks.
AllSign-EventTipo de evento (ej. document.completed), metadata de conveniencia —no forma parte de lo firmado, no lo uses para verificar.
AllSign-Delivery-IdID de la entrega (el envío de este evento a este endpoint), a diferencia de webhook-id, que identifica el evento. También viaja como UUID crudo.
AllSign-Livemodetrue si el evento nació en entorno live, false en sandbox. Viaja en toda entrega, de ambas cohortes (ver cohortes).

El id viaja en dos representaciones. El header webhook-id y el eventId del sobre son el mismo id, pero el header lo trae como UUID crudo (con guiones) y el sobre como evt_<hex> (con prefijo, sin guiones). Un dedup que compare el header contra eventId tal cual nunca va a coincidir, y un check startsWith('evt_') sobre el header rechazaría el 100% de las entregas. Deduplica por uno solo de los dos (recomendado: el eventId del sobre) o normaliza antes de comparar. Lo mismo aplica a AllSign-Delivery-Id: el header trae el UUID crudo y GET /v3/webhooks/{id}/deliveries lista esa misma entrega con su id whd_<hex>.

webhook-id / webhook-timestamp / webhook-signature van en minúsculas —así los define la spec Standard Webhooks (los nombres de cabecera HTTP son case-insensitive de todos modos). AllSign-Event / AllSign-Delivery-Id / AllSign-Livemode siguen el estilo Hyphenated-Pascal-Case del resto de la v3, sin prefijo X- (RFC 6648).

Firma Standard Webhooks

Cada entrega v3 trae la cabecera webhook-signature:

webhook-signature: v1,g0hM9SsE+OTPJTGeGg9CTHqYPnJZQrfE7BMc4b1rBz8=
  • v1 —la versión del esquema de firma (siempre v1 hoy).
  • El valor tras la coma es el HMAC-SHA256 en base64 (no hex).

El material firmado es "{webhook-id}.{webhook-timestamp}." + cuerpo_crudo —el id del evento, un punto, el timestamp, otro punto, y luego los bytes crudos del body tal como llegan (nunca un JSON re-serializado). La clave HMAC es el contenido de tu secreto después de quitarle el prefijo whsec_, decodificado de base64url a bytes crudos —nunca la cadena whsec_… completa en UTF-8.

Ventana de repetición: 5 minutos. Rechaza cualquier entrega cuyo webhook-timestamp difiera más de 300 s de tu reloj. El timestamp viaja en su propia cabecera (webhook-timestamp), no empaquetado dentro de la firma —pero sigue formando parte del material firmado, así que no se puede manipular sin invalidar la firma.

Durante una rotación de secreto la cabecera trae dos tokens separados por espacio: v1,<firma-nueva> v1,<firma-previa>. Verifica contra tus secretos candidatos y acepta si cualquiera hace match —así ninguna entrega falla durante la ventana de 24 h.

Verificar la firma en tu servidor

La forma recomendada es la librería oficial standardwebhooks —ya maneja el parseo de cabeceras, la ventana de repetición y la rotación. Si no puedes agregar la dependencia, este es el fallback manual (Node.js), traducción 1:1 de la fórmula publicada:

import crypto from 'crypto'

// El secreto completo tal como lo devolvió AllSign, incluido el prefijo.
const SECRET = process.env.ALLSIGN_WEBHOOK_SECRET // "whsec_…"
const REPLAY_WINDOW_S = 300

function decodeKey(secret) {
  const raw = secret.replace(/^whsec_/, '')
  return Buffer.from(raw, 'base64url') // llave = secreto SIN el prefijo, decodificado
}

function verifyAllSign(rawBody, id, timestamp, signatureHeader) {
  // Ventana de repetición: rechaza si el webhook-timestamp difiere >300s del reloj.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > REPLAY_WINDOW_S) return false

  const signedContent = Buffer.concat([
    Buffer.from(`${id}.${timestamp}.`, 'utf8'), // "{webhook-id}.{webhook-timestamp}."
    rawBody, // los bytes CRUDOS del body, nunca un JSON re-serializado
  ])
  const expected = crypto
    .createHmac('sha256', decodeKey(SECRET))
    .update(signedContent)
    .digest('base64') // base64, no hex

  // Durante una rotación el header trae varios "v1,<firma>" separados por espacio:
  // acepta si ALGUNO hace match.
  const candidates = signatureHeader
    .split(' ')
    .filter((tok) => tok.startsWith('v1,'))
    .map((tok) => tok.slice(3))

  return candidates.some((sig) => {
    try {
      return crypto.timingSafeEqual(Buffer.from(expected, 'base64'), Buffer.from(sig, 'base64'))
    } catch {
      return false
    }
  })
}

Las dos cohortes: v3 y clásica (v2legacy)

Cada endpoint de webhook lleva una versión de contrato (apiVersion) que decide el formato completo del cable —cuerpo, cabeceras y esquema de firma. Hoy existen dos:

2026-07-11 (v3)v2legacy (clásica)
CuerpoSobre camelCase (el de esta página)snake_case, congelado byte a byte
Cabeceraswebhook-id / webhook-timestamp / webhook-signature + AllSign-Event / AllSign-Delivery-IdX-AllSign-Event / X-AllSign-Event-Id / X-AllSign-Timestamp
FirmaStandard Webhooks: HMAC-SHA256 en base64 sobre "{id}.{timestamp}." + body; llave = el secreto sin whsec_, decodificado de base64urlX-AllSign-Signature (solo si el endpoint tiene HMAC habilitado): HMAC-SHA256 en hex sobre "{timestamp}.{body}", con el ISO-8601 de X-AllSign-Timestamp; llave = los bytes literales del secreto
Entornolivemode en el sobre + cabecera AllSign-LivemodeSolo la cabecera AllSign-Livemode (el cuerpo congelado no trae el campo)

No existe ningún header llamado AllSign-Signature a secas: la cohorte clásica firma con X-AllSign-Signature y la v3 con webhook-signature.

¿En qué cohorte nace un endpoint?

  • POST /v3/webhooks siempre crea v3 (2026-07-11): fuerza un secreto whsec_ y la firma Standard Webhooks. Quien llama esta API pidió v3 explícitamente.
  • Desde el dashboard, el endpoint hereda el formato de tu cuenta: si ya tienes destinos clásicos, el nuevo nace v2legacy —lo más probable es que apunte al handler que ya tienes, y nacer en v3 te lo rompería sin que pidieras nada. Una cuenta que estrena integración nace en v3.

Las dos cohortes nunca se mezclan en una entrega: un evento con ambas audiencias se despacha por separado a cada endpoint, cada uno con su formato, y ningún endpoint recibe el mismo evento dos veces.

Cambiar un endpoint de cohorte hoy solo se puede desde el dashboard: el update por API (PATCH /v3/webhooks/{id}) no expone apiVersion. El cambio aplica a partir del siguiente intento de entrega.

Reintentos y entregas fallidas

Cada intento de entrega tiene un timeout de 10 s —tu endpoint debe responder 2xx dentro de esa ventana (por eso: encola y procesa en background). El cuerpo máximo de una entrega es 50 MiB; un payload que lo exceda se marca fallido sin intentar el POST.

Tu respuestaQué hace AllSign
2xxEntrega SENT. Fin.
4xxFallo permanente: reintentar el mismo payload no va a ayudar, la entrega queda FAILED de inmediato.
5xx, timeout, error de conexiónFallo transitorio: se reintenta con backoff.

El calendario de reintentos depende de la cohorte:

  • v3: backoff exponencial desde 2 s (se duplica en cada intento) con tope de 6 h entre intentos, durante hasta 3 días. Si en 3 días tu endpoint no respondió 2xx, la entrega queda FAILED.
  • Clásica (v2legacy): 8 intentos con backoff exponencial (2 s, 4 s, 8 s… 256 s — unos 8 minutos en total) y después FAILED.

Cada reintento reusa el mismo webhook-id (y el mismo eventId del sobre) con un webhook-timestamp nuevo — la firma se recalcula por intento. El historial de intentos se consulta con GET /v3/webhooks/{id}/deliveries.

Catálogo de eventos

Los eventos v3 son un catálogo congelado y con versión con fecha. document.* cubre el ciclo de vida del documento; nom151.constancia.issued avisa cuando se emite la constancia de conservación. signer.declined ya dispara en sandbox —lo emite el firmante mágico signer-declined@sandbox.allsign.io (ver Entornos)—; en live todavía no, porque la acción de rechazo del firmante aún no está disponible ahí (por eso el catálogo del contrato lo sigue etiquetando reserved).

EventoCategoríaEstadoQué representa
document.createdDocumentsactiveSe creó un documento vía la API.
document.sentDocumentsactiveEl documento salió de creación y entró al ciclo de firma (primeras invitaciones despachadas).
document.completedDocumentsactiveTodas las partes firmaron y el PDF de evidencia está listo (se entrega por URL, no inline en base64).
document.voidedDocumentsactiveEl documento se anuló. La retención NOM-151 conserva el registro.
document.expiredDocumentsactiveEl documento llegó a su fecha límite sin completarse. Trae quién sí alcanzó a firmar.
document.fill_startedDocumentsactiveThe document entered data-fill: role-bound variables are pending.
document.ready_to_signDocumentsactiveThe PDF was materialized with the filled data and is ready to sign.
signer.signedSignersactiveOne signer completed their signature. Carries the running progress.
signer.fill_completedSignersactiveA signer finished filling the variables assigned to their role.
signer.reminder_sentSignersactiveA signing reminder was sent to a signer (email or WhatsApp).
signer.declinedSignersreservedUn firmante rechazó firmar. Ya dispara en sandbox (vía el firmante mágico signer-declined@sandbox.allsign.io); en live aún no hay acción de rechazo.
nom151.constancia.issuedComplianceactiveSe emitió la constancia de conservación NOM-151 de un documento completado.

Payload: document.created

{
  "eventId": "evt_7c9e6679742540de944be07fc1f90ae7",
  "eventType": "document.created",
  "apiVersion": "2026-07-11",
  "occurredAt": "2026-07-11T18:04:00.000Z",
  "tenantId": "550e8400-e29b-41d4-a716-446655440000",
  "livemode": true,
  "data": {
    "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG",
    "name": "Contrato de arrendamiento 2026.pdf",
    "status": "draft",
    "createdAt": "2026-07-11T18:04:00Z",
    "createdViaApi": true,
    "signers": [
      {
        "signerId": "sgr_63db6fa927094f689ea7bc640194bade",
        "name": "Juan Pérez",
        "email": "juan@empresa.com",
        "phone": null,
        "status": "waiting_for_signature"
      }
    ]
  }
}

Payload: document.sent

{
  "eventId": "evt_a1b2c3d4e5f67890abcdef1234567890",
  "eventType": "document.sent",
  "apiVersion": "2026-07-11",
  "occurredAt": "2026-07-11T18:10:00.000Z",
  "tenantId": "550e8400-e29b-41d4-a716-446655440000",
  "livemode": true,
  "data": {
    "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG",
    "name": "Contrato de arrendamiento 2026.pdf",
    "status": "awaiting_signatures",
    "sentAt": "2026-07-11T18:10:00Z",
    "signers": [
      {
        "signerId": "sgr_63db6fa927094f689ea7bc640194bade",
        "email": "juan@empresa.com",
        "phone": null,
        "invitationChannel": "email",
        "invitedAt": "2026-07-11T18:10:00Z"
      }
    ]
  }
}

Payload: document.completed

El PDF de evidencia se entrega por URL (el endpoint estable de la API que acuña una URL prefirmada fresca al acceder), nunca en base64 inline.

{
  "eventId": "evt_b2c3d4e5f6a78901bcdef12345678901",
  "eventType": "document.completed",
  "apiVersion": "2026-07-11",
  "occurredAt": "2026-07-11T19:03:00.000Z",
  "tenantId": "550e8400-e29b-41d4-a716-446655440000",
  "livemode": true,
  "data": {
    "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG",
    "name": "Contrato de arrendamiento 2026.pdf",
    "status": "completed",
    "completedAt": "2026-07-11T19:03:00Z",
    "evidencePdf": {
      "url": "https://api.allsign.io/v3/documents/doc_5Qr9tA3fZwLZmp3D1bCdEfG?expand=evidencePdf",
      "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
      "sizeBytes": 204800,
      "mimeType": "application/pdf"
    },
    "nom151": {
      "constanciaUrl": "https://api.allsign.io/v3/documents/doc_5Qr9tA3fZwLZmp3D1bCdEfG?expand=nom151",
      "serialNumber": "12345",
      "issuedAt": "2026-07-11T19:03:30Z"
    },
    "signers": [
      {
        "signerId": "sgr_63db6fa927094f689ea7bc640194bade",
        "name": "Juan Pérez",
        "email": "juan@empresa.com",
        "signedAt": "2026-07-11T19:02:00Z",
        "authMethod": "FIRMA_ELECTRONICA_SIMPLE"
      }
    ]
  }
}

authMethod es un valor crudo, no un enum cerrado. Viaja tal cual quedó registrado en la firma. Los valores que existen hoy: POR_DEFINIR (el flujo no registró un método específico — el más común), FIRMA_ELECTRONICA_SIMPLE y FIRMA_ELECTRONICA_AVANZADA_SAT (firma con e.firma del SAT). También puede venir null. Trátalo como cadena informativa: no hagas un match estricto ni un switch exhaustivo — pueden aparecer valores nuevos sin cambio de versión. Aplica igual en signer.signed.

Payload: document.voided

{
  "eventId": "evt_c3d4e5f6a7b89012cdef123456789012",
  "eventType": "document.voided",
  "apiVersion": "2026-07-11",
  "occurredAt": "2026-07-11T20:00:00.000Z",
  "tenantId": "550e8400-e29b-41d4-a716-446655440000",
  "livemode": true,
  "data": {
    "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG",
    "status": "voided",
    "voidedAt": "2026-07-11T20:00:00Z",
    "reason": "Reemplazado por una versión corregida",
    "previousStatus": "awaiting_signatures",
    "cancelledSignatures": 1,
    "voidedBy": {
      "actorType": "user",
      "actorId": "usr_1a2b3c4d5e6f7g8h"
    }
  }
}

Payload: document.expired

Fíjate en signers[].signedAt: en null marca a quien no alcanzó a firmar. Un vencimiento con 2 de 3 firmas se resuelve distinto a uno con cero, así que el evento trae el corte completo en vez de obligarte a pedirlo aparte. expiresAt es la fecha que venció y expiredAt el momento en que el sistema lo marcó —no coinciden, porque el barrido corre periódicamente.

{
  "eventId": "evt_e5f6a7b8c9d01234ef12345678901234",
  "eventType": "document.expired",
  "apiVersion": "2026-07-11",
  "occurredAt": "2026-07-12T00:05:00.000Z",
  "tenantId": "550e8400-e29b-41d4-a716-446655440000",
  "livemode": true,
  "data": {
    "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG",
    "name": "Contrato de arrendamiento",
    "status": "expired",
    "expiresAt": "2026-07-11T23:59:59Z",
    "expiredAt": "2026-07-12T00:05:00Z",
    "signedCount": 1,
    "totalSigners": 2,
    "signers": [
      {
        "signerId": "sgr_9f8e7d6c5b4a3210",
        "name": "Ana Ruiz",
        "email": "ana@ejemplo.mx",
        "signedAt": "2026-07-10T16:20:00Z"
      },
      {
        "signerId": "sgr_1a2b3c4d5e6f7080",
        "name": "Beto Lara",
        "email": "beto@ejemplo.mx",
        "signedAt": null
      }
    ]
  }
}

Payload: signer.signed

El evento de avance: dispara cada vez que un firmante completa su firma, con el corte de cuántos van. Si solo te importa el final, usa document.completed; si quieres seguir el progreso, éste es el que buscas.

{
  "eventId": "evt_a1b2c3d4e5f60789ab12cd34ef567890",
  "eventType": "signer.signed",
  "apiVersion": "2026-07-11",
  "occurredAt": "2026-07-12T18:41:02.000Z",
  "tenantId": "550e8400-e29b-41d4-a716-446655440000",
  "livemode": true,
  "data": {
    "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG",
    "name": "Contrato de arrendamiento",
    "signer": {
      "signerId": "sgr_9f8e7d6c5b4a3210",
      "name": "Ana Ruiz",
      "email": "ana@ejemplo.mx",
      "phone": null,
      "signedAt": "2026-07-12T18:41:02Z",
      "authMethod": "FIRMA_ELECTRONICA_SIMPLE"
    },
    "signedCount": 1,
    "totalSigners": 3
  }
}

Payload: document.fill_started

El documento entró a llenado de datos: hay variables asignadas a un rol que alguien debe llenar antes de que empiece la firma. pendingRoles te dice a quién le toca.

{
  "eventId": "evt_b2c3d4e5f6a78901bc23de45f6789012",
  "eventType": "document.fill_started",
  "apiVersion": "2026-07-11",
  "occurredAt": "2026-07-12T16:02:10.000Z",
  "tenantId": "550e8400-e29b-41d4-a716-446655440000",
  "livemode": true,
  "data": {
    "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG",
    "name": "Contrato de arrendamiento",
    "pendingRoles": [
      { "roleId": "9f8e7d6c-5b4a-4321-8765-0fedcba98765", "name": "Arrendatario" }
    ],
    "variablesTotal": 8,
    "variablesPending": 3
  }
}

Payload: signer.fill_completed

Un firmante terminó de llenar las variables de su rol. allFillsComplete en true significa que ya no falta nadie — es la señal de que el documento va a materializarse.

{
  "eventId": "evt_c3d4e5f6a7b89012cd34ef56789abcde",
  "eventType": "signer.fill_completed",
  "apiVersion": "2026-07-11",
  "occurredAt": "2026-07-12T16:20:44.000Z",
  "tenantId": "550e8400-e29b-41d4-a716-446655440000",
  "livemode": true,
  "data": {
    "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG",
    "roleId": "9f8e7d6c-5b4a-4321-8765-0fedcba98765",
    "roleName": "Arrendatario",
    "contactEmail": "beto@ejemplo.mx",
    "filledVariablesCount": 3,
    "completedAt": "2026-07-12T16:20:44Z",
    "allFillsComplete": true
  }
}

Payload: document.ready_to_sign

El PDF se materializó con los datos ya llenados y quedó congelado. A partir de aquí los firmantes ven el documento final, nunca {{variables}} sin resolver.

{
  "eventId": "evt_d4e5f6a7b8c90123de45f6789abcdef0",
  "eventType": "document.ready_to_sign",
  "apiVersion": "2026-07-11",
  "occurredAt": "2026-07-12T16:21:03.000Z",
  "tenantId": "550e8400-e29b-41d4-a716-446655440000",
  "livemode": true,
  "data": {
    "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG",
    "name": "Contrato de arrendamiento",
    "status": "awaiting_signatures",
    "pdfs": [
      { "pdfId": "3f2e1d0c-9b8a-4765-a432-10fedcba9876", "pdfHash": "9c1185a5c5e9fc54612808977ee8f548b2258d31" }
    ]
  }
}

Payload: signer.reminder_sent

Se envió un recordatorio a un firmante que aún no firma. daysRemaining son los días que le quedan antes de que el documento venza.

En la API v2 este evento se llama signature.reminder_sent. En v3 se nombra por el recurso al que se refiere; si migras un endpoint de v2 a v3, el nombre cambia solo.

{
  "eventId": "evt_e5f6a7b8c9d01234ef56789abcdef012",
  "eventType": "signer.reminder_sent",
  "apiVersion": "2026-07-11",
  "occurredAt": "2026-07-14T09:00:00.000Z",
  "tenantId": "550e8400-e29b-41d4-a716-446655440000",
  "livemode": true,
  "data": {
    "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG",
    "signerId": "sgr_1a2b3c4d5e6f7080",
    "channel": "whatsapp",
    "daysRemaining": 3,
    "recipient": "+521234567890",
    "sentAt": "2026-07-14T09:00:00Z"
  }
}

Payload: signer.declined

Ya se emite en sandbox: el firmante mágico signer-declined@sandbox.allsign.io lo dispara, así que puedes probar tu manejador de punta a punta hoy. En live todavía no llega, porque la acción de rechazo del firmante aún no está disponible en producción — suscribirte desde ya no rompe nada.

{
  "eventId": "evt_f6a7b8c9d0e12345f6789abcdef01234",
  "eventType": "signer.declined",
  "apiVersion": "2026-07-11",
  "occurredAt": "2026-07-12T19:15:00.000Z",
  "tenantId": "550e8400-e29b-41d4-a716-446655440000",
  "livemode": true,
  "data": {
    "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG",
    "signer": {
      "signerId": "sgr_1a2b3c4d5e6f7080",
      "name": "Beto Lara",
      "email": "beto@ejemplo.mx"
    },
    "declinedAt": "2026-07-12T19:15:00Z",
    "reason": "El monto no corresponde a lo acordado"
  }
}

Payload: nom151.constancia.issued

{
  "eventId": "evt_d4e5f6a7b8c90123def1234567890123",
  "eventType": "nom151.constancia.issued",
  "apiVersion": "2026-07-11",
  "occurredAt": "2026-07-11T19:03:30.000Z",
  "tenantId": "550e8400-e29b-41d4-a716-446655440000",
  "livemode": true,
  "data": {
    "documentId": "doc_5Qr9tA3fZwLZmp3D1bCdEfG",
    "constancia": {
      "url": "https://api.allsign.io/v3/documents/doc_5Qr9tA3fZwLZmp3D1bCdEfG?expand=nom151",
      "serialNumber": "12345",
      "issuedAt": "2026-07-11T19:03:30Z",
      "algorithm": "SHA256"
    },
    "evidenceSha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
  }
}

El SDK @allsign/sdk (beta —aún no publicado en npm) trae helpers para verificar la firma y tipar el sobre del evento.

Buenas prácticas

  • Verifica la firma en cada entrega antes de procesar (código arriba).
  • Deduplica por eventId —puedes recibir el mismo evento más de una vez.
  • Responde 2xx rápido —el timeout por intento es de 10 s; procesa en background. Un fallo transitorio se reintenta (ver Reintentos).
  • Ramifica por livemode —trata los eventos test y live por caminos separados; nunca mezcles datos de prueba con producción.
  • Usa HTTPS —AllSign solo entrega a URLs https://.
  • Rota el secreto periódicamente con rotate-secret; la ventana de 24 h evita downtime.