Aller au contenu

Webhooks

Bona Fide envoie une requête HTTPS POST à votre endpoint pour chaque événement d’enveloppe auquel vous êtes abonné. Les livraisons suivent la spécification Standard Webhooks : n’importe laquelle de ses bibliothèques peut donc les vérifier.

Dans Paramètres › Webhooks, ou avec une clé API dotée de la portée org:manage :

Fenêtre de terminal
curl https://api.bonafidesign.com/v1/webhook-endpoints \
-H "Authorization: Bearer $BONA_FIDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://app.example.com/hooks/bona-fide",
"description": "Production",
"events": ["envelope.completed", "envelope.sealed", "envelope.declined"]
}'

La réponse contient secret (whsec_…), cette fois-ci uniquement. Une liste events vide abonne l’endpoint à tous les événements. L’URL doit être publique et en https://. Un endpoint appartient à un seul mode : les endpoints créés avec une clé de test ne reçoivent que les événements des enveloppes en mode test, ceux de production uniquement les événements de production.

PATCH /v1/webhook-endpoints/{id} modifie l’URL, les événements ou la description, et met l’endpoint en pause ou le réactive (enabled). POST /v1/webhook-endpoints/{id}/test envoie un ping webhook.test.

Type Quand
envelope.created L’enveloppe a été créée
envelope.ready Le document est généré et l’enveloppe est prête pour ses destinataires
recipient.sent C’est au tour d’un destinataire (et, en cas de remise par e-mail, son lien a été envoyé)
recipient.viewed Un destinataire a ouvert la page de signature pour la première fois
recipient.signed Un destinataire a validé (ou a fait l’objet d’une contresignature automatique)
recipient.declined Un destinataire a refusé
envelope.completed Tous les destinataires ont terminé. data.values contient les variables et toutes les valeurs saisies
envelope.sealed Le PDF scellé est prêt à être téléchargé
envelope.declined L’enveloppe a pris fin parce qu’un destinataire a refusé
envelope.voided Vous avez annulé l’enveloppe
envelope.expired expiresAt est dépassé avant la finalisation
signing_link.used Un visiteur a lancé une enveloppe depuis un lien de signature permanent

acceptance.created est réservé dans le schéma et n’est pas encore émis.

{
"id": "evt_9sKq2Lm4Tz",
"type": "envelope.completed",
"createdAt": "2026-10-13T14:09:52.004Z",
"testMode": false,
"data": {
"envelope": {
"id": "env_8Gm2kQ4rT1",
"status": "completed",
"seal": "pending",
"recipients": [{ "id": "rcp_5Jw0pLs9Xe", "role": "client", "status": "completed", "…": "…" }],
"externalId": "user_8841",
"metadata": {},
"…": "…"
},
"values": {
"variables": { "client": { "name": "Acme Robotics, Inc." }, "effective_date": "2026-10-13" },
"recipients": {
"client": { "client_title": "CEO", "start_date": "2026-11-01" }
}
}
}
}

data.envelope est l’enveloppe complète au moment de l’événement. data.values n’est présent que sur envelope.completed : les variables que vous avez envoyées et les valeurs des champs de chaque rôle, par identifiant de champ.

Chaque livraison comporte :

En-tête Valeur
webhook-id L’identifiant de l’événement (evt_…), identique à chaque nouvelle tentative. Utilisez-le pour dédoublonner
webhook-timestamp Secondes Unix au moment de la signature de cette tentative
webhook-signature v1,<base64 HMAC-SHA256>, séparées par des espaces lorsque plusieurs secrets signent
user-agent BonaFide-Webhooks/1

La signature est un HMAC-SHA256 calculé sur {webhook-id}.{webhook-timestamp}.{raw body}, avec pour clé la partie du secret située après whsec_, décodée en base64. Vérifiez le corps brut (avant l’analyse JSON), comparez en temps constant et rejetez les horodatages qui s’écartent de plus de cinq minutes de votre horloge.

import express from 'express';
import { Webhook } from 'standardwebhooks'; // npm i standardwebhooks
const wh = new Webhook(process.env.BONA_FIDE_WEBHOOK_SECRET!); // whsec_…
const app = express();
app.post('/hooks/bona-fide', express.raw({ type: 'application/json' }), (req, res) => {
let event;
try {
event = wh.verify(req.body, req.headers as Record<string, string>); // throws if forged or too old
} catch {
return res.sendStatus(400);
}
queue.add(event); // do the work asynchronously and answer quickly
res.sendStatus(204);
});

Une livraison réussit sur toute réponse 2xx reçue dans les 10 secondes. Les redirections ne sont pas suivies. Dans tous les autres cas, la livraison est relancée avec un délai croissant : après 5 secondes, 5 minutes, 30 minutes, 2 heures, 5 heures, 10 heures, 14 heures, 20 heures et 24 heures, pendant trois jours au maximum après l’événement. La livraison est ensuite marquée comme échouée.

  • Répondez rapidement et traitez de manière asynchrone ; dédoublonnez sur webhook-id.
  • Les livraisons peuvent arriver dans le désordre : vérifiez data.envelope.status plutôt que de supposer une séquence.
  • GET /v1/webhook-endpoints/{id}/deliveries liste les tentatives récentes avec leurs codes d’état, erreurs et durées, et chaque endpoint affiche son taux de réussite sur 24 heures (health).
  • POST /v1/events/{id}/redeliver renvoie un événement à un endpoint, par exemple après une panne.

POST /v1/webhook-endpoints/{id}/secret/rotate renvoie immédiatement un nouveau secret. Pendant les 24 heures qui suivent, chaque livraison est signée avec les deux secrets, le nouveau et l’ancien (deux entrées dans webhook-signature), ce qui vous permet de déployer le nouveau secret sans perdre d’événements.