Webhooks
A Bona Fide envia um POST HTTPS para o seu endpoint por cada evento de envelope que subscrever. As entregas seguem
a especificação Standard Webhooks, pelo que qualquer uma das respetivas
bibliotecas as pode verificar.
Registar um endpoint
Seção intitulada “Registar um endpoint”Em Definições › Webhooks ou com uma chave de API que tenha o âmbito
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"] }'A resposta inclui o secret (whsec_…), apenas desta vez. Uma lista events vazia subscreve todos os eventos.
O URL tem de ser https:// público. Um endpoint pertence a um único modo: os endpoints criados com uma chave de
teste só recebem eventos de envelopes do modo de teste, e os de produção só recebem eventos de produção.
PATCH /v1/webhook-endpoints/{id} altera o URL, os eventos ou a descrição e coloca o endpoint em pausa ou retoma-o
(enabled). POST /v1/webhook-endpoints/{id}/test envia um ping webhook.test.
Eventos
Seção intitulada “Eventos”| Tipo | Quando |
|---|---|
envelope.created |
O envelope foi criado |
envelope.ready |
O documento foi gerado e o envelope está pronto para os destinatários |
recipient.sent |
Chegou a vez de um destinatário (e, com entrega por e-mail, a respetiva ligação foi enviada) |
recipient.viewed |
Um destinatário abriu a página de assinatura pela primeira vez |
recipient.signed |
Um destinatário submeteu (ou foi contra-assinado automaticamente) |
recipient.declined |
Um destinatário recusou |
envelope.completed |
Todos os destinatários terminaram. data.values contém as variáveis e todos os valores introduzidos |
envelope.sealed |
O PDF selado está pronto para transferência |
envelope.declined |
O envelope terminou porque um destinatário recusou |
envelope.voided |
Anulou o envelope |
envelope.expired |
O expiresAt passou antes da conclusão |
signing_link.used |
Um visitante iniciou um envelope a partir de uma ligação de assinatura permanente |
acceptance.created está reservado no esquema e ainda não é emitido.
Payload
Seção intitulada “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 é o envelope completo no momento do evento. data.values só existe em
envelope.completed: as variáveis que enviou e os valores dos campos de cada função, por id de campo.
Cabeçalhos e assinatura
Seção intitulada “Cabeçalhos e assinatura”Cada entrega inclui:
| Cabeçalho | Valor |
|---|---|
webhook-id |
O id do evento (evt_…); é o mesmo em todas as repetições. Utilize-o para eliminar duplicados |
webhook-timestamp |
Segundos Unix em que esta tentativa foi assinada |
webhook-signature |
v1,<base64 HMAC-SHA256>, separados por espaços quando assina mais do que um segredo |
user-agent |
BonaFide-Webhooks/1 |
A assinatura é um HMAC-SHA256 sobre {webhook-id}.{webhook-timestamp}.{raw body}, com a chave formada pela parte do
segredo a seguir a whsec_, descodificada de base64. Verifique o corpo em bruto (antes da análise do JSON),
compare em tempo constante e rejeite marcas temporais que difiram mais de cinco minutos do seu relógio.
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);}Repetições
Seção intitulada “Repetições”Uma entrega é bem-sucedida com qualquer 2xx no prazo de 10 segundos. Os redirecionamentos não são seguidos.
Qualquer outra resposta dá origem a repetições com backoff, ao fim de 5 segundos, 5 minutos, 30 minutos, 2 horas,
5 horas, 10 horas, 14 horas, 20 horas e 24 horas, durante no máximo três dias após o evento. Depois disso, a entrega
é marcada como falhada.
- Responda depressa e processe de forma assíncrona; elimine duplicados com base no
webhook-id. - As entregas podem chegar fora de ordem: verifique
data.envelope.statusem vez de presumir uma sequência. GET /v1/webhook-endpoints/{id}/deliverieslista as tentativas recentes com códigos de estado, erros e tempos, e cada endpoint mostra a sua taxa de sucesso nas últimas 24 horas (health).POST /v1/events/{id}/redelivervolta a enviar um evento para um endpoint, por exemplo após uma indisponibilidade.
Rotação do segredo
Seção intitulada “Rotação do segredo”POST /v1/webhook-endpoints/{id}/secret/rotate devolve imediatamente um novo segredo. Durante as 24 horas
seguintes, todas as entregas são assinadas com ambos os segredos, o novo e o antigo (duas entradas em
webhook-signature), para que possa implementar o novo segredo sem perder eventos.