Webhooki
Bona Fide wysyła żądanie HTTPS POST do Twojego punktu końcowego dla każdego subskrybowanego zdarzenia koperty. Dostarczenia są zgodne
ze specyfikacją Standard Webhooks, więc może je zweryfikować dowolna z jej bibliotek.
Rejestrowanie punktu końcowego
Dział zatytułowany „Rejestrowanie punktu końcowego”W sekcji Ustawienia › Webhooki lub za pomocą klucza API z zakresem 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"] }'Odpowiedź zawiera secret (whsec_…) – tylko ten jeden raz. Pusta lista events subskrybuje wszystkie zdarzenia.
Adres URL musi być publicznym adresem https://. Punkt końcowy należy do jednego trybu: punkty końcowe utworzone kluczem testowym otrzymują tylko
zdarzenia kopert w trybie testowym, a produkcyjne – tylko zdarzenia produkcyjne.
PATCH /v1/webhook-endpoints/{id} zmienia adres URL, zdarzenia lub opis oraz wstrzymuje lub wznawia punkt końcowy
(enabled). POST /v1/webhook-endpoints/{id}/test wysyła testowy ping webhook.test.
Zdarzenia
Dział zatytułowany „Zdarzenia”| Typ | Kiedy |
|---|---|
envelope.created |
Koperta została utworzona |
envelope.ready |
Dokument jest wyrenderowany, a koperta gotowa dla odbiorców |
recipient.sent |
Nadeszła kolej odbiorcy (a przy dostarczaniu e-mailem wysłano mu link) |
recipient.viewed |
Odbiorca po raz pierwszy otworzył stronę podpisywania |
recipient.signed |
Odbiorca przesłał podpis (lub nastąpiła automatyczna kontrasygnata) |
recipient.declined |
Odbiorca odmówił |
envelope.completed |
Wszyscy odbiorcy zakończyli. data.values zawiera zmienne i wszystkie wpisane wartości |
envelope.sealed |
Zapieczętowany PDF jest gotowy do pobrania |
envelope.declined |
Koperta zakończyła się, ponieważ odbiorca odmówił |
envelope.voided |
Unieważniono kopertę |
envelope.expired |
expiresAt minęło przed zakończeniem |
signing_link.used |
Odwiedzający rozpoczął kopertę ze stałego linku do podpisu |
acceptance.created jest zarezerwowane w schemacie i jeszcze nie jest emitowane.
Ładunek
Dział zatytułowany „Ładunek”{ "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 to pełna koperta w chwili zdarzenia. data.values występuje tylko w
envelope.completed: wysłane przez Ciebie zmienne oraz wartości pól każdej roli według identyfikatora pola.
Nagłówki i podpis
Dział zatytułowany „Nagłówki i podpis”Każde dostarczenie zawiera:
| Nagłówek | Wartość |
|---|---|
webhook-id |
Identyfikator zdarzenia (evt_…); taki sam przy każdym ponowieniu. Używaj go do deduplikacji |
webhook-timestamp |
Czas podpisania tej próby w sekundach uniksowych |
webhook-signature |
v1,<base64 HMAC-SHA256>, rozdzielane spacjami, gdy podpisuje więcej niż jeden sekret |
user-agent |
BonaFide-Webhooks/1 |
Podpis to HMAC-SHA256 obliczony z {webhook-id}.{webhook-timestamp}.{raw body} z kluczem w postaci zdekodowanej z base64 części
sekretu po whsec_. Weryfikuj surową treść (przed parsowaniem JSON), porównuj w stałym czasie i odrzucaj
znaczniki czasu odbiegające od Twojego zegara o więcej niż pięć minut.
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);}Ponawianie
Dział zatytułowany „Ponawianie”Dostarczenie kończy się powodzeniem przy dowolnej odpowiedzi 2xx w ciągu 10 sekund. Przekierowania nie są wykonywane. Każda inna odpowiedź powoduje ponawianie
z narastającym odstępem: po 5 sekundach, 5 minutach, 30 minutach, 2 godzinach, 5 godzinach, 10 godzinach, 14 godzinach, 20 godzinach i 24 godzinach,
najdłużej przez trzy dni od zdarzenia. Następnie dostarczenie jest oznaczane jako nieudane.
- Odpowiadaj szybko, a przetwarzaj asynchronicznie; deduplikuj według
webhook-id. - Dostarczenia mogą nadchodzić w innej kolejności: sprawdzaj
data.envelope.status, zamiast zakładać określoną sekwencję. GET /v1/webhook-endpoints/{id}/deliverieswyświetla ostatnie próby z kodami statusu, błędami i czasami, a każdy punkt końcowy pokazuje swój wskaźnik powodzenia z ostatnich 24 godzin (health).POST /v1/events/{id}/redeliverponownie wysyła zdarzenie do punktu końcowego, na przykład po awarii.
Rotacja sekretu
Dział zatytułowany „Rotacja sekretu”POST /v1/webhook-endpoints/{id}/secret/rotate natychmiast zwraca nowy sekret. Przez kolejne 24 godziny każde
dostarczenie jest podpisywane zarówno nowym, jak i starym sekretem (dwa wpisy w webhook-signature), dzięki czemu możesz
wdrożyć nowy sekret bez utraty zdarzeń.