Webhooks
Bona Fide envoie une requête HTTPS POST à votre point de terminaison pour chaque événement d’enveloppe auquel vous
êtes abonné. Les livraisons respectent la spécification Standard Webhooks :
n’importe laquelle de ses bibliothèques peut donc les vérifier.
Enregistrer un point de terminaison
Section intitulée « Enregistrer un point de terminaison »Dans Paramètres › Webhooks, ou avec une clé d’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 le secret (whsec_…), cette fois seulement. Une liste events vide abonne le point de
terminaison à tous les événements. L’URL doit être publique et en https://. Un point de terminaison appartient à un
seul mode : les points de terminaison créés avec une clé de test ne reçoivent que les événements des enveloppes en
mode test, et ceux de production, que les événements de production.
PATCH /v1/webhook-endpoints/{id} modifie l’URL, les événements ou la description, et suspend ou réactive le point
de terminaison (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 devenu le tour d’un destinataire (et, avec la livraison par courriel, 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 soumis (ou a été contresigné automatiquement) |
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 |
La date expiresAt est passée avant la finalisation |
signing_link.used |
Un visiteur a lancé une enveloppe à partir d’un lien de signature permanent |
acceptance.created est réservé dans le schéma, mais 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 dans 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_…); le même à chaque nouvelle tentative. Utilisez-le pour dédoublonner |
webhook-timestamp |
Le moment, en secondes Unix, où cette tentative a été signée |
webhook-signature |
v1,<base64 HMAC-SHA256>, séparés par des espaces quand 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 qui suit 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 un délai de 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 au plus trois jours après
l’événement. La livraison est ensuite marquée comme ayant échoué.
- Répondez rapidement et traitez de façon asynchrone; dédoublonnez selon
webhook-id. - Les livraisons peuvent arriver dans le désordre : vérifiez
data.envelope.statusplutôt que de présumer une séquence. GET /v1/webhook-endpoints/{id}/deliveriesliste les tentatives récentes avec les codes d’état, les erreurs et les durées, et chaque point de terminaison affiche son taux de réussite sur 24 heures (health).POST /v1/events/{id}/redeliverenvoie de nouveau un événement à un point de terminaison, 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
suivantes, chaque livraison est signée avec à la fois le nouveau et l’ancien secret (deux entrées dans
webhook-signature) : vous pouvez ainsi déployer le nouveau secret sans perdre d’événements.