Webhooks
Bona Fide sendet für jedes abonnierte Umschlag-Event einen HTTPS-POST an Ihren Endpunkt. Die Zustellungen folgen
der Spezifikation Standard Webhooks, sodass jede ihrer Bibliotheken sie
verifizieren kann.
Endpunkt registrieren
Abschnitt betitelt „Endpunkt registrieren“Unter Einstellungen › Webhooks oder mit einem API-Schlüssel, der den
Scope org:manage hat:
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"] }'Die Antwort enthält secret (whsec_…), nur dieses eine Mal. Eine leere events-Liste abonniert alle Events.
Die URL muss öffentlich und https:// sein. Ein Endpunkt gehört zu einem Modus: Mit einem Testschlüssel erstellte
Endpunkte erhalten nur Events von Umschlägen im Testmodus, Live-Endpunkte nur Live-Events.
PATCH /v1/webhook-endpoints/{id} ändert URL, Events oder Beschreibung und pausiert oder reaktiviert den Endpunkt
(enabled). POST /v1/webhook-endpoints/{id}/test sendet einen webhook.test-Ping.
| Typ | Wann |
|---|---|
envelope.created |
Der Umschlag wurde erstellt |
envelope.ready |
Das Dokument ist gerendert und der Umschlag bereit für seine Empfänger |
recipient.sent |
Ein Empfänger ist an der Reihe (und bei Zustellung per E-Mail wurde sein Link gesendet) |
recipient.viewed |
Ein Empfänger hat die Signaturseite zum ersten Mal geöffnet |
recipient.signed |
Ein Empfänger hat abgesendet (oder es wurde automatisch gegengezeichnet) |
recipient.declined |
Ein Empfänger hat abgelehnt |
envelope.completed |
Alle Empfänger sind fertig. data.values enthält die Variablen und alle eingegebenen Werte |
envelope.sealed |
Das versiegelte PDF steht zum Download bereit |
envelope.declined |
Der Umschlag endete, weil ein Empfänger abgelehnt hat |
envelope.voided |
Sie haben den Umschlag storniert |
envelope.expired |
expiresAt ist vor dem Abschluss verstrichen |
signing_link.used |
Ein Besucher hat über einen dauerhaften Signaturlink einen Umschlag gestartet |
acceptance.created ist im Schema reserviert und wird noch nicht ausgelöst.
Nutzdaten
Abschnitt betitelt „Nutzdaten“{ "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 ist der vollständige Umschlag zum Zeitpunkt des Events. data.values gibt es
nur bei envelope.completed: die von Ihnen gesendeten Variablen und die Feldwerte jeder Rolle nach Feld-ID.
Header und Signatur
Abschnitt betitelt „Header und Signatur“Jede Zustellung enthält:
| Header | Wert |
|---|---|
webhook-id |
Die Event-ID (evt_…); bei jeder Wiederholung gleich. Verwenden Sie sie zur Deduplizierung |
webhook-timestamp |
Unix-Sekunden, zu denen dieser Versuch signiert wurde |
webhook-signature |
v1,<base64 HMAC-SHA256>, durch Leerzeichen getrennt, wenn mehr als ein Secret signiert |
user-agent |
BonaFide-Webhooks/1 |
Die Signatur ist ein HMAC-SHA256 über {webhook-id}.{webhook-timestamp}.{raw body}, mit dem base64-dekodierten Teil
des Secrets nach whsec_ als Schlüssel. Verifizieren Sie den rohen Body (vor dem JSON-Parsing), vergleichen Sie
in konstanter Zeit und weisen Sie Zeitstempel zurück, die mehr als fünf Minuten von Ihrer Uhr abweichen.
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);}Wiederholungen
Abschnitt betitelt „Wiederholungen“Eine Zustellung ist erfolgreich bei jedem 2xx innerhalb von 10 Sekunden. Weiterleitungen werden nicht verfolgt.
Alles andere wird mit Backoff wiederholt, nach 5 Sekunden, 5 Minuten, 30 Minuten, 2 Stunden, 5 Stunden, 10 Stunden,
14 Stunden, 20 Stunden und 24 Stunden, höchstens drei Tage nach dem Event. Danach wird die Zustellung als
fehlgeschlagen markiert.
- Antworten Sie schnell und verarbeiten Sie asynchron; deduplizieren Sie anhand von
webhook-id. - Zustellungen können in anderer Reihenfolge eintreffen: Prüfen Sie
data.envelope.status, statt eine Abfolge vorauszusetzen. GET /v1/webhook-endpoints/{id}/deliverieslistet die letzten Versuche mit Statuscodes, Fehlern und Laufzeiten auf, und jeder Endpunkt zeigt seine Erfolgsquote der letzten 24 Stunden (health).POST /v1/events/{id}/redeliversendet ein Event erneut an einen Endpunkt, etwa nach einem Ausfall.
Secret rotieren
Abschnitt betitelt „Secret rotieren“POST /v1/webhook-endpoints/{id}/secret/rotate liefert sofort ein neues Secret. In den nächsten 24 Stunden wird
jede Zustellung mit beiden Secrets signiert, dem neuen und dem alten (zwei Einträge in webhook-signature),
sodass Sie das neue Secret ausrollen können, ohne Events zu verlieren.