Aller au contenu principal

Webhooks sur Legalesign

Qu’est-ce que les webhooks

Les webhooks sont vos URL où Legalesign enverra des mises à jour de statut en temps réel. Vous pouvez créer vos propres URL ou des systèmes d'automatisation (comme MS Power Automate) peuvent les générer pour déclencher un flux de travail.

Par exemple, dans cette démo, nous créons une URL avec Microsoft Power Automate. Nous ajoutons cette URL comme webhook dans Legalesign pour le cas où des documents sont signés. Le flux Power Automate se déclenche lorsqu’un document est signé et enregistre automatiquement ces PDF nouvellement signés dans Sharepoint. En savoir plus sur MS Power Automate.

info

Note importante – vous devez avoir les permissions Admin de l’équipe pour recevoir les événements d’équipe envoyés à votre webhook.

Pourquoi vous avez besoin de webhooks

Les webhooks vous permettent de maintenir un système basé sur les événements. Les webhooks vous tiendront informé de tous les événements au sein de vos groupes. Vous pouvez les utiliser pour créer votre propre tableau de bord en direct et/ou pour synchroniser avec votre propre base de données.

Comme mentionné ci-dessus, les webhooks générés par les systèmes d’automatisation tels que Power Automate vous permettent de déclencher des procédures de stockage, des emails, la tenue de registres, etc., ce qui, combiné à l’API, fournit tout ce dont vous avez besoin pour l’automatisation des processus.

Alternative websocket en temps réel

Un flux de souscription websocket en temps réel est également disponible. Cela peut être une meilleure option lorsque vous souhaitez des mises à jour côté client sans exposer un endpoint webhook. Le websocket expose un ensemble plus large d’événements que les webhooks.

Voir : Démarrage rapide Websocket en temps réel

astuce

Oui, les webhooks semblent fastidieux, mais ils en valent vraiment la peine et sont excellents pour votre intégration. Lisez la suite.

Types de webhook

  • Événements en temps réel
  • Tous les événements toutes les 6 minutes (héritage)
  • Lors de [l’événement] (héritage)

Pour les webhooks hérités, voir : webhooks hérités

info

Les webhooks sont le plus souvent utilisés pour télécharger un PDF signé. Lorsque vous créez un webhook, appliquez le filtre d’événement 'Final PDF created'. Dans votre code, la requête entrante request.body est en JSON, parsez-la et extrayez documentId = ['data']['uuid']. Ensuite, lancez la requête API pour télécharger le PDF – GET https://eu-api.legalesign.com/api/v1/pdf/${documentId}/.

Comment ajouter ou supprimer un webhook

Ajouter ou supprimer un webhook via l’application web

Accédez au tableau de bord API et cliquez sur la section Webhooks. Le formulaire propose des contrôles simples pour ajouter ou supprimer des webhooks. Vous pouvez isoler les événements webhook à certaines équipes et/ou événements.

Capture d'écran section Webhooks

danger

Vos webhooks reçoivent des événements de tous les comptes où vous êtes administrateur, aussi bien en dev qu’en prod. Utilisez le filtre de groupe pour créer différents webhooks pour vos différents groupes.

Ajouter ou supprimer un webhook via l’API REST

Voir la section 'webhook' en bas de la navigation à gauche dans la documentation API : Webhooks REST API

Ajouter ou supprimer un webhook via GraphQL

Nouveau ! Si vous utilisez l’authentification SRP et l’interface GraphQL, vous avez tous les droits CRUD. Découvrez Legalesign GraphiQL Explorer. Les webhooks peuvent être listés comme un attribut du type User, et il existe des mutations pour createWebhook, updateWebhook et deleteWebhook.

Que contient un webhook ?

Le moyen le plus rapide d’inspecter vos données est d’ajouter un webhook, d’envoyer / signer / rejeter quelques documents de test dans l’application web, puis de consulter le journal des webhooks dans le Tableau de bord API Legalesign.

Si vous avez besoin d’un URL webhook temporaire pour les tests, essayez ngrok.

Petit tutoriel sur ngrok : une fois que vous avez téléchargé ngrok, lancez-le dans le terminal avec ./ngrok http 80 et il vous donnera une adresse https. Entrez-la comme votre webhook. Ouvrez maintenant http://127.0.0.1:4040. Voilà, vous commencerez à voir tous les webhooks et leurs données. Commencez à envoyer et signer des documents tests. Note : Ngrok peut renvoyer une erreur 5XX, donc vous pouvez vous attendre à plusieurs tentatives et messages d’erreur puisque le système considère le statut 5XX comme une tentative échouée.

info

Assurez-vous que votre code de réception du webhook renvoie une réponse de succès 2XX. En cas de plusieurs exceptions possibles dans votre code, renvoyez différents codes statut 5XX. Les logs des webhooks dans le tableau de bord API vous indiqueront ensuite quelle exception a été déclenchée.

Format du webhook en temps réel

Le format des données peut appartenir à l’un des deux schémas : soit un schéma ‘document’ soit un schéma ‘recipient’. Inspectez l’attribut 'object' pour déterminer le schéma.

Les deux schémas ont aussi un attribut ‘event’. Vous pouvez filtrer par objet et facultativement par événement lors de la création du webhook.

Un tableau de tous les objets et événements possibles est ci-dessous. Cette image illustre un objet document (pour un évènement 'created'). Les schémas JSON complets sont en annexe au bas de cet article.

données document dans ngrok

Les requêtes arrivent en POST avec un corps JSON. Notez que le contenu de 'data' peut s’enrichir avec plus de champs au fil du temps.

astuce

Utilisez les attributs 'tag'. Vous pouvez définir jusqu’à 3 tags lors de la création d’un document. En utilisant les tags, vous n’aurez peut-être pas besoin de sauvegarder les identifiants Legalesign. Vous pouvez aussi utiliser un tag pour stocker un secret afin de vérifier la requête entrante (merci Themis pour cette suggestion).

Tableau des combinaisons possibles pour 'object' et 'event' :

objectevent
documentcreated
documentrejected
documentfinalPdfCreated
recipientcompleted
recipientrejected
recipientemailOpened
recipientvisiting
recipientbounced
recipientautoReminderEmail
recipientemailNotification

La plupart des événements ne se produisent qu’une fois. Les exceptions sont visiting, bounced, emailNotification et autoReminderEmail, ils peuvent se produire plusieurs fois.

attention

N’oubliez pas de désactiver la protection CSRF pour votre vue qui reçoit ces requêtes POST.

Voici un exemple d’objet 'recipient', avec l'événement 'bounced' :

données recipient dans ngrok

Remarquez emailBounce et emailBounceMessage dans 'data'. 'emailBounceMessage' indique le type de rebond comme suit :

  • Rebond dur : "Message hard bounced"
  • Rebond doux : "Email soft bounced (either out of office or a timeout)"
  • Retardé : "Message delayed (check email domain exists)"
info

Le schéma peut recevoir à tout moment des attributs supplémentaires.

Déboguez les webhooks dans le tableau de bord API

Tous les webhooks sont enregistrés et vous pouvez examiner leur contenu ainsi que le code de statut http dans votre tableau de bord API. Pour en savoir plus, consultez le tutoriel du tableau de bord

Déclenchement manuel de webhook

Déclenchez manuellement un webhook via le formulaire en bas de la page Webhooks dans votre Portail développeur.

Assurez-vous d’utiliser un vrai destinataire ou identifiant de document, selon l’événement.

Codes de statut

Cliquez sur ces liens pour voir la table de référence des statuts de document et statuts de destinataire.

Vérifier un webhook

Le webhook signe le paquet de données avec une clé privée. Vous pouvez vérifier avec la clé publique - télécharger la clé publique.

  • L’emplacement de la chaîne de signature se trouve dans l’en-tête X-Signed-SHA256.
  • Les données signées correspondent au corps complet de la requête (en tant que chaîne).

La ‘signature’ est encodée en base64. Voici un exemple de code de vérification 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');

Optionnellement, une vérification basée sur HMAC est aussi disponible. Pour HMAC, une clé secrète pour vos webhooks vous est partagée. La valeur signée arrivera dans l’en-tête X-HMAC-SHA256.

Il existe beaucoup de ressources en ligne montrant comment vérifier une valeur HMAC. Exemple de vérification en python serait:

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

Ou exemple de code Salesforce Apex pour vérifier un HMAC :

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));

Plus de webhooks ?

Si vous avez besoin de plus de webhooks pour d’autres événements, ou plus de données dans ceux-ci, contactez-nous et faites passer votre demande dans le pipeline de développement.

Référence des schémas

Il existe deux schémas possibles, l’un pour l’objet ‘document’ et un autre pour l’objet ‘recipient’. Les deux schémas ont des clés de premier niveau ‘object’ et ‘event’. Inspectez l’attribut ‘object’ pour déterminer le schéma concerné. Ou utilisez un filtre webhook pour ne retourner que des documents ou uniquement des objets destinataires.

Veuillez noter que les schémas peuvent recevoir de nouveaux attributs à tout moment (mais sans jamais en perdre).

1. Schéma JSON pour l’objet ‘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. Schéma JSON pour l’objet ‘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"
]
}