Webhooks
A Bona Fide envia um POST HTTPS para o seu endpoint a cada evento de envelope em que você se inscreve. As entregas
seguem a especificação Standard Webhooks, então qualquer uma das bibliotecas
dela pode verificá-las.
Cadastrando um endpoint
Seção intitulada “Cadastrando um endpoint”Em Configurações › Webhooks ou com uma chave de API que tenha o escopo
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_…), somente desta vez. Uma lista events vazia inscreve o endpoint em
todos os eventos. A URL precisa ser https:// pública. Um endpoint pertence a um único modo: endpoints criados com
uma chave de teste recebem apenas eventos de envelopes do modo de teste, e os de produção, apenas eventos de produção.
PATCH /v1/webhook-endpoints/{id} altera a URL, os eventos ou a descrição e pausa ou retoma o endpoint
(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 renderizado e o envelope está pronto para os destinatários |
recipient.sent |
Chegou a vez de um destinatário (e, com entrega por e-mail, o link dele foi enviado) |
recipient.viewed |
Um destinatário abriu a página de assinatura pela primeira vez |
recipient.signed |
Um destinatário enviou (ou foi contra-assinado automaticamente) |
recipient.declined |
Um destinatário recusou |
envelope.completed |
Todos os destinatários terminaram. data.values traz as variáveis e todos os valores preenchidos |
envelope.sealed |
O PDF selado está pronto para download |
envelope.declined |
O envelope foi encerrado porque um destinatário recusou |
envelope.voided |
Você anulou o envelope |
envelope.expired |
O expiresAt passou antes da conclusão |
signing_link.used |
Um visitante iniciou um envelope a partir de um link 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ó aparece em
envelope.completed: as variáveis que você enviou e os valores dos campos de cada papel, por id de campo.
Cabeçalhos e assinatura
Seção intitulada “Cabeçalhos e assinatura”Cada entrega traz:
| Cabeçalho | Valor |
|---|---|
webhook-id |
O id do evento (evt_…); o mesmo em todas as novas tentativas. Use-o para deduplicar |
webhook-timestamp |
Segundos Unix em que esta tentativa foi assinada |
webhook-signature |
v1,<base64 HMAC-SHA256>, separados por espaço quando mais de um segredo assina |
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 depois de whsec_, decodificada de base64. Verifique o corpo bruto (antes do parsing do JSON), compare
em tempo constante e rejeite carimbos de data e hora com mais de cinco minutos de diferença em relação ao 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);}Novas tentativas
Seção intitulada “Novas tentativas”Uma entrega é bem-sucedida com qualquer 2xx em até 10 segundos. Redirecionamentos não são seguidos. Qualquer outra
resposta gera novas tentativas com backoff, depois de 5 segundos, 5 minutos, 30 minutos, 2 horas, 5 horas, 10 horas,
14 horas, 20 horas e 24 horas, por no máximo três dias após o evento. Depois disso, a entrega é marcada como falha.
- Responda rápido e processe de forma assíncrona; deduplique pelo
webhook-id. - As entregas podem chegar fora de ordem: confira
data.envelope.statusem vez de presumir uma sequência. GET /v1/webhook-endpoints/{id}/deliverieslista as tentativas recentes com códigos de status, erros e tempos, e cada endpoint mostra a taxa de sucesso das últimas 24 horas (health).POST /v1/events/{id}/redeliverenvia um evento a um endpoint de novo, por exemplo depois de uma indisponibilidade.
Rotação do segredo
Seção intitulada “Rotação do segredo”POST /v1/webhook-endpoints/{id}/secret/rotate retorna um novo segredo imediatamente. Nas 24 horas seguintes, toda
entrega é assinada com os dois segredos, o novo e o antigo (duas entradas em webhook-signature), para que você
possa implantar o novo segredo sem perder eventos.