Skip to content
Developers

Webhooks

With webhooks, bb-sign sends a signed JSON payload to your URL each time an envelope changes status, and your system reacts immediately without polling.

The signature scheme is defined normatively in the webhook signature specification; this page is the integration guide.

In Settings, tab Webhooks, select New webhook.

  • Endpoint URL: “Must be HTTPS and reachable from the internet. We refuse private and internal addresses.” The check runs again on every delivery.
  • Events to receive: select the ones you need from the six available:
Event When
envelope.sent An envelope was sent to its signers
envelope.completed Every signer has signed
envelope.cancelled An envelope was cancelled
envelope.expired An envelope reached its expiry without being completed
signer.signed One signer signed; others may be pending
signer.viewed A signer opened the document

The signing secret is shown once, when the webhook is created. Store it in your secret store. An organization can have up to 20 webhooks.

{
"id": "3f8c...",
"type": "envelope.completed",
"apiVersion": "2026-08-09",
"createdAt": "2026-08-09T14:22:31Z",
"data": {
"envelope": {
"id": "...", "title": "Q3 Agreement", "status": "COMPLETED",
"createdAt": "...", "expiresAt": null
},
"signer": { "id": "...", "email": "...", "name": "...", "signOrder": 1, "status": "SIGNED", "signedAt": "..." }
}
}

data.signer is included only in signer.* events. Full schemas are in the API reference as WebhookEvent, so a generated client receives them typed.

Every delivery carries three headers:

Header Use
X-BBSign-Signature The signature you verify. t=<unix>,v1=<hex>
X-BBSign-Event-Id Your idempotency key, stable across retries
X-BBSign-Event-Type The event name, so you can route before parsing the body

When you receive envelope.completed, call GET /api/v1/envelopes/{id}/documents to get the file. The response includes signedUrl once the envelope is COMPLETED, valid for 3,600 s. The payload deliberately carries no download links: a presigned link in a payload would remain usable after delivery and could end up stored in logs. The walkthrough shows the whole sequence.

The signature covers the exact bytes received. Most web frameworks parse the JSON before your handler sees it, and re-serializing that object produces different bytes (key order and whitespace change), so verification fails even though the object is correct. Capture the raw body before any parsing.

import { verifyWebhookSignature } from '@binarybridges/bb-sign-sdk/webhooks'
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf } }))
app.post('/webhooks/bb-sign', (req, res) => {
if (!verifyWebhookSignature(req.get('X-BBSign-Signature'), req.rawBody, SECRET)) {
return res.sendStatus(401)
}
res.sendStatus(200)
void handle(JSON.parse(req.rawBody.toString('utf8')))
})

In Next.js, use await request.text() instead of .json(). In Fastify, register a content-type parser with parseAs: 'buffer'. The JavaScript SDK page includes both examples in full.

The specification is normative and short. Three requirements deserve particular attention:

  1. Compare in constant time. == on the digest reveals, byte by byte, how much of a forged signature is correct, which is enough to construct a valid one.
  2. Check the timestamp. Without a freshness check, a captured delivery could be replayed indefinitely.
  3. Accept any matching v1. During a secret rotation the header carries two values; requiring the first to match defeats the rotation window.

The test vectors contain 16 cases, including the ones that must fail. The server and both SDKs are tested against that same file; test your verifier against it as well.

Respond 2xx immediately and process afterwards. Acknowledge receipt as soon as the signature is valid and process the event asynchronously.

Retries. A failed delivery is retried on a backoff schedule spanning roughly 15 hours, with delays of 1m, 5m, 30m, 2h, 12h. Any 2xx counts as success; any other response counts as a failure.

Automatic disabling. After 3 consecutive deliveries that exhaust their retries, the subscription is disabled and the organization’s admins are emailed. The panel shows the status Disabled automatically, with the note: “We disabled this endpoint after its retries failed repeatedly. Fix the endpoint, then enable it again.” bb-sign also probes the endpoint every 6 h, and a successful response re-enables it.

Events while disabled. They are recorded as Not sent and are not queued for later, so your system does not receive a flood of stale events when it comes back. The history shows exactly which events were not delivered.

At-least-once delivery. The same event can arrive twice, for example after a network blip or a restart. X-BBSign-Event-Id is identical in both cases; use it to deduplicate.

Stable body across retries. The body is rendered once and resent byte for byte, so an envelope.sent retried hours later describes the moment of sending. Only the signature is recomputed, because it includes the delivery time.

In Settings, tab Webhooks, History shows every event, every attempt, the response code and the error. Entries are kept for 90 days. It is the first place to diagnose a delivery:

Status Meaning Action
Delivered The event arrived and your endpoint returned 2xx None
Rejected / Unreachable Your endpoint returned an error or did not respond; the attempt list shows which Check your logs for that attempt; the next retry is already scheduled
Gave up Retries were exhausted Fix the endpoint and fetch the envelope’s status with the API; this event is not resent
Not sent The endpoint was disabled at the time Enable the endpoint and reconcile the period with GET /api/v1/envelopes
Could not be built An error on bb-sign’s side Contact support

In Settings, tab Webhooks, select Rotate secret. “Both the old and the new secret will be accepted for a grace period, so your receiver keeps working until you redeploy it with the new one.” The grace period is 24 h. Deliveries in that window carry two v1 values; a correct verifier accepts either, as both SDKs do.

Send test delivers a synthetic event to your endpoint and shows the response status code. It uses the same signing process as real deliveries, so it validates your verifier end to end.