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.
Enregistrer un endpoint
Section intitulée « Enregistrer un endpoint »Dans Paramètres › Webhooks, ou avec une clé API dotée de la portée
org:manage :
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.
Événements
Section intitulée « Événements »| 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.
Charge utile
Section intitulée « Charge utile »{ "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.
En-têtes et signature
Section intitulée « En-têtes et signature »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);});# pip install standardwebhooks flaskimport osfrom flask import Flask, requestfrom standardwebhooks.webhooks import Webhook
wh = Webhook(os.environ["BONA_FIDE_WEBHOOK_SECRET"]) # whsec_…app = Flask(__name__)
@app.post("/hooks/bona-fide")def bona_fide(): try: event = wh.verify(request.get_data(), dict(request.headers)) except Exception: return "", 400 enqueue(event) return "", 204import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(secret: string, headers: Record<string, string>, rawBody: string) { const id = headers['webhook-id']; const ts = Number(headers['webhook-timestamp']); if (!id || !ts || Math.abs(Date.now() / 1000 - ts) > 300) throw new Error('stale or missing headers'); const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64'); const expected = createHmac('sha256', key).update(`${id}.${ts}.${rawBody}`).digest(); const ok = (headers['webhook-signature'] ?? '').split(' ').some((part) => { const [version, sig] = part.split(','); const given = Buffer.from(sig ?? '', 'base64'); return version === 'v1' && given.length === expected.length && timingSafeEqual(given, expected); }); if (!ok) throw new Error('bad signature'); return JSON.parse(rawBody);}Nouvelles tentatives
Section intitulée « Nouvelles tentatives »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.statusplutôt que de supposer une séquence. GET /v1/webhook-endpoints/{id}/deliveriesliste 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}/redeliverrenvoie un événement à un endpoint, par exemple après une panne.
Rotation du secret
Section intitulée « Rotation du secret »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.