Saltar al contenido principal

Webhooks en Legalesign

Qué son los webhooks

Los webhooks son tus URLs donde Legalesign enviará actualizaciones de estado en tiempo real. Puedes crear tus propias URLs o sistemas de automatización (como MS Power Automate) pueden generarlas para iniciar un flujo de trabajo.

Por ejemplo, en esta demostración creamos una URL con Microsoft Power Automate. Añadimos esa URL como webhook en Legalesign para cuando los documentos son firmados. El flujo de Power Automate se activa cuando un documento es firmado y guarda automáticamente esos PDFs recién firmados en Sharepoint. Aprende más sobre MS Power Automate.

info

Nota importante - debes tener permisos de Administrador de equipo para que los eventos de equipo se envíen a tu webhook.

Por qué necesitas webhooks

Los webhooks te permiten mantener un sistema basado en eventos. Los webhooks te actualizarán sobre todos los eventos dentro de tus grupos. Podrías usarlos para crear tu propio panel en vivo y/o para sincronizar con tu propia base de datos.

Como se mencionó anteriormente, los webhooks generados desde sistemas de automatización como Power Automate te permiten iniciar procedimientos de almacenamiento, correos electrónicos, mantenimiento de registros, etc., que, combinados con la API, proporcionan todo lo que necesitas para la automatización de procesos.

Alternativa websocket en tiempo real

También está disponible un feed de suscripción de websocket en tiempo real. Esto puede ser más adecuado cuando quieres actualizaciones en vivo del lado del cliente sin exponer un endpoint de webhook. El websocket expone un conjunto más amplio de eventos que los webhooks.

Ver: Inicio rápido de Websocket en Tiempo Real

sugerencia

Sí, los webhooks parecen un lío, pero valen mucho la pena y son geniales para tu integración. Sigue leyendo.

Tipos de webhook

  • Eventos en tiempo real
  • Todos los eventos cada 6 minutos (legado)
  • Al ocurrir un [evento] (legado)

Para webhooks legados ver: webhooks legados

info

Los webhooks se usan comúnmente para descargar un PDF firmado. Cuando creas el webhook aplica el filtro de evento 'Final PDF created'. En tu código, la solicitud entrante request.body es JSON, analízala y extrae documentId = ['data']['uuid']. Ahora emite la consulta API para descargar el PDF - GET https://eu-api.legalesign.com/api/v1/pdf/${documentId}/.

Cómo añadir o eliminar un webhook

Añadir o eliminar un webhook usando la aplicación web

Ve a API dashboard y accede a la sección Webhooks. El formulario tiene controles simples para añadir o eliminar webhooks. Opcionalmente, puedes aislar eventos de webhook a ciertos equipos y/o eventos.

Captura de pantalla de la sección Webhooks

peligro

Tus webhooks reciben eventos de todas las cuentas donde eres administrador, tanto de desarrollo como de producción. Usa el filtro de grupo para crear diferentes webhooks para tus varios grupos.

Añadir o eliminar un webhook usando la API REST

Consulta la sección 'webhook' al final del menú lateral izquierdo en la documentación de la API: REST API webhooks

Añadir o eliminar un webhook usando GraphQL

¡Nuevo! Si usas autenticación SRP y la interfaz GraphQL, tienes poderes CRUD completos. Revisa Legalesign GraphiQL Explorer. Los webhooks pueden listarse como un atributo del tipo User, y hay mutaciones para createWebhook, updateWebhook y deleteWebhook.

¿Qué contiene un webhook?

La forma más rápida de inspeccionar tus datos es añadir un webhook, enviar/firmar/rechazar algunos documentos de prueba en la aplicación web, y luego ver el registro de webhooks en el Legalesign API Dashboard.

Si necesitas una URL de webhook temporal para pruebas revisa ngrok.

Cómo usar ngrok: una vez que descargues ngrok, ejecútalo en el terminal con ./ngrok http 80 y te dará una dirección https. Introduce eso como tu webhook. Ahora abre http://127.0.0.1:4040. Eso es todo, empezarás a ver todos los webhooks y sus datos. Empieza a enviar y firmar documentos de prueba. Nota: Ngrok puede devolver un error 5XX, por lo que puedes esperar ver múltiples intentos y mensajes de error ya que el sistema considerará que el estado 5XX es un intento fallido.

info

Asegúrate de que tu código receptor del webhook devuelve una respuesta de éxito 2XX. Si tienes varias excepciones posibles en tu código devuelve diferentes códigos de estado 5XX. Los registros de webhook en el dashboard de API te mostrarán cuál excepción fue activada.

El formato del webhook en tiempo real

El formato de los datos puede ser uno de dos esquemas, un esquema de 'documento' o un esquema de 'destinatario'. Inspecciona el atributo 'object' para determinar con cuál esquema estás trabajando.

Ambos esquemas también tienen un atributo 'event'. Puedes filtrar por objeto y opcionalmente por evento cuando creas el webhook.

Una tabla con todos los posibles objetos y eventos está abajo. Esta imagen ilustra un objeto documento (para un evento 'created'). Los esquemas JSON completos están añadidos al final de este artículo.

datos del documento en ngrok

Las solicitudes llegan como una petición POST que contiene JSON. Ten en cuenta que el contenido de 'data' puede ampliarse para incluir más campos con el tiempo.

sugerencia

Usa los atributos 'tag'. Puedes configurar hasta 3 etiquetas cuando creas un documento. Usando etiquetas quizá no necesites almacenar los IDs de Legalesign. También puedes usar etiquetas para guardar un secreto y verificar la solicitud entrante (gracias Themis por esa sugerencia).

Tabla de posibles combinaciones para 'object' y 'event':

objetoevento
documentcreated
documentrejected
documentfinalPdfCreated
recipientcompleted
recipientrejected
recipientemailOpened
recipientvisiting
recipientbounced
recipientautoReminderEmail
recipientemailNotification

La mayoría de los eventos solo ocurren una vez. Las excepciones son visiting, bounced, emailNotification y autoReminderEmail, que pueden ocurrir muchas veces.

aviso

No olvides desactivar la protección CSRF para la vista que recibe estas solicitudes POST.

Aquí hay un ejemplo de un objeto 'recipient', con evento 'bounced':

datos del destinatario en ngrok

Observa emailBounce y emailBounceMessage en 'data'. 'emailBounceMessage' indicará el tipo de rebote como sigue:

  • Rebote duro: "Message hard bounced"
  • Rebote suave: "Email soft bounced (either out of office or a timeout)"
  • Retrasado: "Message delayed (check email domain exists)"
info

El esquema puede tener nuevos atributos añadidos en cualquier momento.

Depurar webhooks en el API dashboard

Todos los webhooks quedan registrados y puedes examinar su contenido y el código de estado http en tu API dashboard. Para aprender más consulta el tutorial del dashboard

Disparar manualmente un webhook

Dispara manualmente un webhook usando el formulario al final de la página Webhooks en tu Developer Portal.

Asegúrate de usar un destinatario o documento real, según el evento.

Códigos de estado

Haz clic en estos enlaces para ver la tabla de referencia para el estado del documento y estado del destinatario.

Verificar un webhook

El webhook firma el paquete de datos con una clave privada. Puedes verificarlo con la clave pública - descargar clave pública.

  • La ubicación de la cadena de firma está en el encabezado X-Signed-SHA256.
  • Los datos que se firman son todo el request.body (como una cadena).

La 'firma' está codificada en base64. Aquí un código de muestra para verificación en node.js:

const crypto = require('crypto');
const fs = require('fs');

const cert = fs.readFileSync('/location/of/downloadedCert.crt', 'utf8');
const c = new crypto.X509Certificate(cert);
const k = c.publicKey
let verifier = crypto.createVerify('SHA256');
let rawdata = 'sentdata' //JSON stringify data in request.body
let sha256signature = 'xxx' //value of 'X-Signed-Sha256' request header.
verifier.update(rawdata);
return verifier.verify(k, sha256signature, 'base64');

Opcionalmente está disponible también la verificación basada en HMAC. Para HMAC se comparte una clave secreta para tus webhooks. El valor firmado llegará en el encabezado X-HMAC-SHA256.

Hay muchos recursos en línea que demuestran cómo verificar un valor HMAC. Un ejemplo de verificación en python sería:

import hmac, hashlib
sentdata = 'sentdata' # JSON stringify data in request.body
hmacvalue = 'xxx' # value of 'X-HMAC-Sha256' in request header.
v = hmac.new('your secret value'.encode(), 'sentdata'.encode(), hashlib.sha256)
isValid = v.hexdigest() == hmacvalue

O un ejemplo de código Apex para Salesforce para verificar un HMAC es:

String message = 'JSONstring'; //sent in request.body
String privatekey = 'privateKey'; //shared value you have been given
String hmacvalue = 'xxx'; //value of 'X-HMAC-Sha256' in request header.
Boolean verified = Crypto.verifyHMac('HmacSHA256', Blob.valueOf(message), Blob.valueOf(privatekey), EncodingUtil.convertFromHex(hmacvalue));

¿Más webhooks?

Si necesitas más webhooks para eventos diferentes, o más datos en ellos, contáctanos y pon tu solicitud en la cola de desarrollo.

Referencia del esquema

Hay dos posibles esquemas, uno para el objeto 'document' y otro para el objeto 'recipient'. Ambos esquemas tienen claves de nivel superior 'object' y 'event'. Inspecciona el atributo 'object' para determinar con cuál esquema estás tratando. O usa un filtro de webhook para devolver solo objetos documento o solo destinatarios.

Ten en cuenta que al esquema se le pueden añadir nuevos atributos en cualquier momento (pero no disminuirán).

1. Esquema JSON para el objeto 'document'

{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"version": {
"type": "string",
"default": "1.0.0"
},
"object": {
"type": "string",
"default": "document"
},
"created": {
"type": "integer",
"description": "unix timestamp"
},
"id": {
"type": "string",
"format": "uuid"
},
"event": {
"type": "string",
"pattern": "^(created|rejected|finalPdfCreated)$"
},
"data": {
"type": "object",
"properties": {
"tag1": {
"type": "string"
},
"recipients": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"uuid": {
"type": "string",
"format": "uuid"
},
"email": {
"type": "string",
"format": "email"
},
"order": {
"type": "integer"
},
"status": {
"type": "integer"
"pattern": "^(4|5|10|15|20|30|35|39|40|50|60)$"
},
"lastname": {
"type": "string"
},
"roleText": {
"type": "string",
"pattern": "^(signer|approver|witness)$"
},
"firstname": {
"type": "string"
},
"statusText": {
"type": "string",
"pattern": "^(unsent|scheduled|sent|email opened|visited|fields complete|fields complete excluding signature|witnessing required|completed|download final document|rejected|withdrawn)$"
},
"resourceUri": {
"type": "string",
"format": "uri-reference"
},
"rejectReason": {
"type": "string"
}
},
"required": [
"uuid",
"email",
"order",
"status",
"lastname",
"roleText",
"firstname",
"statusText",
"resourceUri",
"rejectReason"
]
}
]
},
"groupResourceUri": {
"type": "string",
"format": "uri-reference"
},
"statusText": {
"type": "string",
"pattern": "^(available|fields complete|completed|removed|rejected|unknown)$"
},
"name": {
"type": "string"
},
"tag": {
"type": "string"
},
"resourceUri": {
"type": "string",
"format": "uri-reference"
},
"uuid": {
"type": "string",
"format": "uuid"
},
"tag2": {
"type": "string"
},
"group": {
"type": "string"
},
"status": {
"type": "integer",
"pattern": "^(10|20|30|40|50)$"
}
},
"required": [
"tag1",
"recipients",
"groupResourceUri",
"statusText",
"name",
"tag",
"resourceUri",
"uuid",
"tag2",
"group",
"status"
]
}
},
"required": [
"version",
"object",
"data",
"created",
"id",
"event"
]
}

2. Esquema JSON para el objeto 'recipient'

{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"version": {
"type": "string",
"default": "1.0.0"
},
"object": {
"type": "string",
"default": "recipient"
},
"created": {
"type": "integer",
"format": "date-time"
},
"id": {
"type": "string",
"format": "uuid"
},
"event": {
"type": "string",
"pattern": "^(emailOpened|bounced|visiting|rejected|completed|autoReminderEmail|emailNotification)$"
},
"data": {
"type": "object",
"properties": {
"tag": {
"type": "string"
},
"tag1": {
"type": "string"
},
"tag2": {
"type": "string"
},
"uuid": {
"type": "string",
"format": "uuid"
},
"email": {
"type": "string",
"format": "email"
},
"group": {
"type": "string",
"format": "uuid"
},
"order": {
"type": "integer"
},
"status": {
"type": "integer",
"pattern": "^(4|5|10|15|20|30|35|39|40|50|60|70)$"
},
"document": {
"type": "string"
},
"documentName": {
"type": "string"
},
"lastname": {
"type": "string"
},
"roleText": {
"type": "string"
},
"firstname": {
"type": "string"
},
"statusText": {
"type": "string",
"pattern": "^(unsent|scheduled|sent|email opened|visited|fields complete|fields complete excluding signature|witnessing required|completed|download final document|rejected|withdrawn)$"
},
"emailBounce": {
"type": "integer"
},
"resourceUri": {
"type": "string",
"format": "uri-reference"
},
"rejectReason": {
"type": "string"
},
"groupResourceUri": {
"type": "string",
"format": "uri-reference"
},
"emailBounceMessage": {
"type": "string"
},
"documentResourceUri": {
"type": "string",
"format": "uri-reference"
}
},
"required": [
"tag",
"tag1",
"tag2",
"uuid",
"email",
"group",
"order",
"status",
"document",
"documentName",
"lastname",
"roleText",
"firstname",
"statusText",
"emailBounce",
"resourceUri",
"rejectReason",
"groupResourceUri",
"emailBounceMessage",
"documentResourceUri"
]
}
},
"required": [
"version",
"object",
"data",
"created",
"id",
"event"
]
}