콘텐츠로 이동

웹훅

Bona Fide는 구독한 봉투 이벤트마다 엔드포인트로 HTTPS POST를 보냅니다. 전송은 Standard Webhooks 사양을 따르므로 이 사양의 어떤 라이브러리로도 검증할 수 있습니다.

설정 › 웹훅에서 등록하거나, org:manage 스코프가 있는 API 키로 등록합니다.

터미널 창
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"]
}'

응답에는 secret(whsec_…)이 포함되며, 이때 한 번만 제공됩니다. events 목록을 비우면 모든 이벤트를 구독합니다. URL은 공개된 https://여야 합니다. 엔드포인트는 하나의 모드에 속합니다. 테스트 키로 만든 엔드포인트는 테스트 모드 봉투의 이벤트만, 라이브 엔드포인트는 라이브 이벤트만 받습니다.

PATCH /v1/webhook-endpoints/{id}는 URL, 이벤트, 설명을 변경하고 엔드포인트를 일시 중지하거나 재개합니다 (enabled). POST /v1/webhook-endpoints/{id}/test는 webhook.test 핑을 보냅니다.

유형 발생 시점
envelope.created 봉투가 생성됨
envelope.ready 문서가 렌더링되어 봉투가 수신자를 맞을 준비가 됨
recipient.sent 수신자의 차례가 됨(이메일 전달의 경우 해당 수신자에게 링크가 전송됨)
recipient.viewed 수신자가 서명 페이지를 처음 엶
recipient.signed 수신자가 제출함(또는 자동으로 연서명됨)
recipient.declined 수신자가 거절함
envelope.completed 모든 수신자가 완료함. data.values에 변수와 입력된 모든 값이 포함됨
envelope.sealed 봉인된 PDF를 다운로드할 수 있음
envelope.declined 수신자가 거절하여 봉투가 종료됨
envelope.voided 봉투를 무효화함
envelope.expired 완료되기 전에 expiresAt이 지남
signing_link.used 방문자가 상시 서명 링크에서 봉투를 시작함

acceptance.created는 스키마에 예약되어 있지만 아직 발생하지 않습니다.

{
"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는 이벤트 발생 시점의 전체 봉투입니다. data.values는 envelope.completed에만 있으며, 보낸 변수와 각 역할의 필드 값(필드 ID별)이 담겨 있습니다.

각 전송에는 다음 헤더가 포함됩니다.

헤더 값
webhook-id 이벤트 ID(evt_…). 재시도할 때도 같습니다. 중복 제거에 사용하세요
webhook-timestamp 이 시도에 서명한 시각(Unix 초)
webhook-signature v1,<base64 HMAC-SHA256>. 둘 이상의 시크릿으로 서명하면 공백으로 구분됩니다
user-agent BonaFide-Webhooks/1

서명은 {webhook-id}.{webhook-timestamp}.{raw body}에 대한 HMAC-SHA256이며, 키는 시크릿에서 whsec_ 뒤 부분을 base64 디코딩한 값입니다. (JSON 파싱 전의) 원시 본문을 검증하고, 상수 시간으로 비교하며, 서버 시계와 5분 넘게 차이 나는 타임스탬프는 거부하세요.

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);
});

10초 안에 2xx가 반환되면 전송에 성공한 것입니다. 리디렉션은 따라가지 않습니다. 그 밖의 경우에는 백오프를 적용해 재시도하며, 5초, 5분, 30분, 2시간, 5시간, 10시간, 14시간, 20시간, 24시간 후에 다시 시도하고, 이벤트 발생 후 최대 3일 동안 계속합니다. 그 후에는 전송이 실패로 표시됩니다.

  • 빠르게 응답하고 처리는 비동기로 하세요. 중복 제거에는 webhook-id를 사용하세요.
  • 전송은 순서가 바뀌어 도착할 수 있습니다. 순서를 가정하지 말고 data.envelope.status를 확인하세요.
  • GET /v1/webhook-endpoints/{id}/deliveries는 최근 시도를 상태 코드, 오류, 소요 시간과 함께 나열하며, 각 엔드포인트에는 24시간 성공률(health)이 표시됩니다.
  • POST /v1/events/{id}/redeliver는 예를 들어 장애 이후에 이벤트를 엔드포인트로 다시 보냅니다.

POST /v1/webhook-endpoints/{id}/secret/rotate는 새 시크릿을 즉시 반환합니다. 이후 24시간 동안 모든 전송은 새 시크릿과 이전 시크릿 모두로 서명되므로(webhook-signature에 항목 두 개), 이벤트를 놓치지 않고 새 시크릿을 배포할 수 있습니다.