Webhook
Bona Fide invia una richiesta HTTPS POST al Suo endpoint per ogni evento di busta a cui si è iscritto. Le consegne
seguono la specifica Standard Webhooks, quindi qualsiasi sua libreria può
verificarle.
Registrazione di un endpoint
Sezione intitolata “Registrazione di un endpoint”In Impostazioni › Webhook, oppure con una chiave API che ha l’ambito
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 risposta include secret (whsec_…), solo questa volta. Un elenco events vuoto iscrive a tutti gli eventi.
L’URL deve essere un indirizzo https:// pubblico. Un endpoint appartiene a una sola modalità: gli endpoint creati con
una chiave di prova ricevono solo gli eventi delle buste in modalità di prova, quelli di produzione solo gli eventi di
produzione.
PATCH /v1/webhook-endpoints/{id} cambia URL, eventi o descrizione e sospende o riattiva l’endpoint (enabled).
POST /v1/webhook-endpoints/{id}/test invia un ping webhook.test.
| Tipo | Quando |
|---|---|
envelope.created |
La busta è stata creata |
envelope.ready |
Il documento è stato composto e la busta è pronta per i destinatari |
recipient.sent |
È arrivato il turno di un destinatario (e, con la consegna via e-mail, il suo link è stato inviato) |
recipient.viewed |
Un destinatario ha aperto la pagina di firma per la prima volta |
recipient.signed |
Un destinatario ha inviato (o è stato controfirmato automaticamente) |
recipient.declined |
Un destinatario ha rifiutato |
envelope.completed |
Tutti i destinatari hanno terminato. data.values contiene le variabili e tutti i valori inseriti |
envelope.sealed |
Il PDF sigillato è pronto per il download |
envelope.declined |
La busta si è chiusa perché un destinatario ha rifiutato |
envelope.voided |
Lei ha annullato la busta |
envelope.expired |
expiresAt è trascorso prima del completamento |
signing_link.used |
Un visitatore ha avviato una busta da un link di firma sempre attivo |
acceptance.created è riservato nello schema e non viene ancora emesso.
Payload
Sezione intitolata “Payload”{ "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 è la busta completa al momento dell’evento. data.values è presente solo in
envelope.completed: le variabili che ha inviato e i valori dei campi di ogni ruolo, per id del campo.
Header e firma
Sezione intitolata “Header e firma”Ogni consegna contiene:
| Header | Valore |
|---|---|
webhook-id |
L’id dell’evento (evt_…); è lo stesso a ogni nuovo tentativo. Lo usi per la deduplicazione |
webhook-timestamp |
Secondi Unix del momento in cui questo tentativo è stato firmato |
webhook-signature |
v1,<base64 HMAC-SHA256>, separati da spazi quando firma più di un segreto |
user-agent |
BonaFide-Webhooks/1 |
La firma è un HMAC-SHA256 su {webhook-id}.{webhook-timestamp}.{raw body}, con chiave pari alla parte del segreto dopo
whsec_, decodificata da base64. Verifichi il corpo grezzo (prima del parsing JSON), esegua il confronto in tempo
costante e rifiuti i timestamp che si discostano di oltre cinque minuti dal Suo orologio.
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);}Nuovi tentativi
Sezione intitolata “Nuovi tentativi”Una consegna riesce con qualsiasi 2xx entro 10 secondi. I reindirizzamenti non vengono seguiti. In tutti gli altri
casi la consegna viene ritentata con backoff, dopo 5 secondi, 5 minuti, 30 minuti, 2 ore, 5 ore, 10 ore, 14 ore,
20 ore e 24 ore, per al massimo tre giorni dopo l’evento. Dopodiché la consegna viene contrassegnata come non riuscita.
- Risponda rapidamente ed elabori in modo asincrono; esegua la deduplicazione su
webhook-id. - Le consegne possono arrivare fuori ordine: controlli
data.envelope.statusinvece di presupporre una sequenza. GET /v1/webhook-endpoints/{id}/deliverieselenca i tentativi recenti con codici di stato, errori e tempi, e ogni endpoint mostra la propria percentuale di successo nelle ultime 24 ore (health).POST /v1/events/{id}/redeliverinvia di nuovo un evento a un endpoint, ad esempio dopo un’interruzione del servizio.
Rotazione del segreto
Sezione intitolata “Rotazione del segreto”POST /v1/webhook-endpoints/{id}/secret/rotate restituisce subito un nuovo segreto. Per le 24 ore successive ogni
consegna viene firmata con entrambi i segreti, il nuovo e il vecchio (due voci in webhook-signature), così può
distribuire il nuovo segreto senza perdere eventi.