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?
- Registras una URL
https://de tu servidor conPOST /v3/webhooksy eliges los eventos. - AllSign te devuelve un secreto de firma (
whsec_…) una sola vez. Guárdalo —no se vuelve a mostrar. - Cuando ocurre un evento, AllSign hace un
POSTa tu URL con el sobre del evento en el body y las cabeceras de firma Standard Webhooks (webhook-id/webhook-timestamp/webhook-signature). - Tu servidor verifica la firma, deduplica por
eventId, procesa el evento y responde2xxrá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": { }
}
| Campo | Descripción |
|---|---|
eventId | ID ú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). |
eventType | El tipo de evento (ej. document.completed). Coincide con la cabecera AllSign-Event. |
apiVersion | Versión de contrato con fecha que congela la forma del data. |
occurredAt | Cuándo ocurrió (ISO-8601 UTC con sufijo Z, precisión de milisegundos). |
tenantId | Tu tenant. |
livemode | true si el evento nació en entorno live. |
data | El 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.
| Cabecera | Descripción |
|---|---|
webhook-id | ID 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-timestamp | Unix timestamp (segundos) con el que se firmó este intento. Un reintento trae uno nuevo. |
webhook-signature | La firma —ver Firma Standard Webhooks. |
AllSign-Event | Tipo de evento (ej. document.completed), metadata de conveniencia —no forma parte de lo firmado, no lo uses para verificar. |
AllSign-Delivery-Id | ID 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-Livemode | true 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 (siemprev1hoy).- 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) | |
|---|---|---|
| Cuerpo | Sobre camelCase (el de esta página) | snake_case, congelado byte a byte |
| Cabeceras | webhook-id / webhook-timestamp / webhook-signature + AllSign-Event / AllSign-Delivery-Id | X-AllSign-Event / X-AllSign-Event-Id / X-AllSign-Timestamp |
| Firma | Standard Webhooks: HMAC-SHA256 en base64 sobre "{id}.{timestamp}." + body; llave = el secreto sin whsec_, decodificado de base64url | X-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 |
| Entorno | livemode en el sobre + cabecera AllSign-Livemode | Solo 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/webhookssiempre crea v3 (2026-07-11): fuerza un secretowhsec_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 respuesta | Qué hace AllSign |
|---|---|
2xx | Entrega SENT. Fin. |
4xx | Fallo permanente: reintentar el mismo payload no va a ayudar, la entrega queda FAILED de inmediato. |
5xx, timeout, error de conexión | Fallo 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 quedaFAILED. - Clásica (
v2legacy): 8 intentos con backoff exponencial (2 s, 4 s, 8 s… 256 s — unos 8 minutos en total) y despuésFAILED.
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).
| Evento | Categoría | Estado | Qué representa |
|---|---|---|---|
document.created | Documents | active | Se creó un documento vía la API. |
document.sent | Documents | active | El documento salió de creación y entró al ciclo de firma (primeras invitaciones despachadas). |
document.completed | Documents | active | Todas las partes firmaron y el PDF de evidencia está listo (se entrega por URL, no inline en base64). |
document.voided | Documents | active | El documento se anuló. La retención NOM-151 conserva el registro. |
document.expired | Documents | active | El documento llegó a su fecha límite sin completarse. Trae quién sí alcanzó a firmar. |
document.fill_started | Documents | active | The document entered data-fill: role-bound variables are pending. |
document.ready_to_sign | Documents | active | The PDF was materialized with the filled data and is ready to sign. |
signer.signed | Signers | active | One signer completed their signature. Carries the running progress. |
signer.fill_completed | Signers | active | A signer finished filling the variables assigned to their role. |
signer.reminder_sent | Signers | active | A signing reminder was sent to a signer (email or WhatsApp). |
signer.declined | Signers | reserved | Un 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.issued | Compliance | active | Se 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
2xxrápido —el timeout por intento es de 10 s; procesa en background. Un fallo transitorio se reintenta (ver Reintentos). - Ramifica por
livemode—trata los eventostestylivepor 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.