JavaScript SDK
The JavaScript SDK connects your Node services to the bb-sign API: it manages tokens, retries
safely and verifies webhook signatures. The package is
@binarybridges/bb-sign-sdk, version 0.1.0.
Requires Node 20 or newer and is ESM only. Use import; require()
is not supported.
Installation
Section titled “Installation”The SDK is delivered as the tarball binarybridges-bb-sign-sdk-0.1.0.tgz, produced with
npm pack. Install it in your project:
npm install ../path/to/binarybridges-bb-sign-sdk-0.1.0.tgzQuickstart
Section titled “Quickstart”Credentials are created in bb-sign under Settings, tab API credentials; the secret is shown once, at creation. Read them from environment variables, outside your source code.
import { createClient } from '@binarybridges/bb-sign-sdk'
const client = createClient({ baseUrl: process.env.BBSIGN_BASE_URL, tokenUrl: process.env.BBSIGN_TOKEN_URL, clientId: process.env.BBSIGN_CLIENT_ID, clientSecret: process.env.BBSIGN_CLIENT_SECRET,})
const envelope = await client.envelopes.createEnvelope({ createEnvelopeRequest: { title: 'Q3 Agreement', otpRequired: false, expiresAt: new Date(Date.now() + 72 * 3600 * 1000), signers: [{ email: 'signer@example.com', name: 'A Signer', signOrder: 1 }], },})
console.log('Created', envelope.id)Tokens are fetched, cached and refreshed automatically. Concurrent calls share a single token
request. The client exposes envelopes, documents and verification.
List by page
Section titled “List by page”for await (const envelope of client.eachPage( (page) => client.envelopes.listEnvelopes({ page, size: 50 }), (p) => p.content,)) { console.log(envelope.title)}Pages are fetched on demand, so iterating over thousands of envelopes does not load them all into memory.
Verify a webhook
Section titled “Verify a webhook”import { verifyWebhookSignature } from '@binarybridges/bb-sign-sdk/webhooks'Capture the raw body
Section titled “Capture the raw body”The signature covers the exact bytes received. The most widely used Node frameworks parse the JSON by default and hand you an object; re-serializing it produces different bytes, and verification fails even though the object is correct. Capture the raw body as shown below.
Express
import express from 'express'
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf }, // keep the bytes}))
app.post('/webhooks/bb-sign', (req, res) => { if (!verifyWebhookSignature(req.get('X-BBSign-Signature'), req.rawBody, SECRET)) { return res.sendStatus(401) } const event = JSON.parse(req.rawBody.toString('utf8')) res.sendStatus(200) // ack first, work after void handle(event)})Next.js (App Router)
export async function POST(request) { const rawBody = await request.text() // text(), not json() if (!verifyWebhookSignature(request.headers.get('x-bbsign-signature'), rawBody, SECRET)) { return new Response(null, { status: 401 }) } const event = JSON.parse(rawBody) return new Response(null, { status: 200 })}Fastify: register a content-type parser that keeps the buffer:
fastify.addContentTypeParser('application/json', { parseAs: 'buffer' }, (req, body, done) => { req.rawBody = body done(null, JSON.parse(body.toString('utf8')))})The helper accepts a string or a Buffer. A parsed object produces a type error instead of
being silently converted with JSON.stringify, which would yield bytes different from those
signed.
Boolean result, no exceptions
Section titled “Boolean result, no exceptions”The helper receives data that may come from an attacker. It therefore always returns a boolean: an exception would produce a 500 in your webhook route and reveal why verification failed.
Respond immediately, process afterwards
Section titled “Respond immediately, process afterwards”Return 2xx as soon as the signature is valid. bb-sign retries on a schedule spanning roughly 15 hours, and after 3 consecutive exhausted schedules it disables the endpoint. Process the event asynchronously so the response does not depend on load.
Deduplicate on the event id
Section titled “Deduplicate on the event id”X-BBSign-Event-Id is stable across retries and redeliveries. Delivery is at-least-once; use this
value as an idempotency key.
Secret rotation
Section titled “Secret rotation”While a rotation window is open, the header carries two v1= values and either verifies. This lets
you deploy the new secret without downtime, with no extra configuration.
Test the handler
Section titled “Test the handler”Verification is time-sensitive: a signature older than 300 s is rejected, so a fixed fixture stops being valid. Pin the clock in tests:
verifyWebhookSignature(header, rawBody, secret, { nowSeconds: 1760000000 })A receiver behind a queue can widen the window with toleranceSeconds.
Retries
Section titled “Retries”GET requests are retried on 5xx errors and transport failures, with exponential backoff and full
jitter.
A POST whose response was not received is not retried. If createEnvelope times out, the
outcome is unknown: the envelope may already exist, and a retry would create a second agreement
sent to the same people. The API does not use idempotency keys. The Java SDK behaves the same way.
If a create times out, list before you retry:
try { await client.envelopes.createEnvelope({ createEnvelopeRequest: request })} catch (error) { if (error.statusCode === 0) { // Never reached bb-sign, or the answer was lost. Check before sending again. }}429 and 503 are also retried for POST, because they indicate the server did not process the
request.
Errors
Section titled “Errors”Every failure throws a BbSignError with a statusCode. 0 indicates the request got no answer.
| Status | Meaning |
|---|---|
| 400 | Malformed request or failed validation |
| 401 | Credentials rejected. The SDK refreshes and retries once before surfacing this |
| 403 | Authenticated, but this credential lacks the organization permission |
| 404 | The resource does not exist, or belongs to another organization or a workspace the credential cannot see. Deliberately indistinguishable |
| 409 | The resource is not in a state that allows the operation |
| 429 | An organization quota is exhausted |
The error carries a status and a message, without the Response object: logging it does not
expose your Authorization header. Messages never include your secret, even when bb-sign’s
response contains it. Errors includes the full table and the error
body.
Security guarantees
Section titled “Security guarantees”- Credentials stay out of logs. The SDK has no logging hook that could record them.
- TLS is always verified. Certificate verification cannot be disabled.
- No duplicate writes. A write with an unknown outcome is not retried (see Retries).
- Webhook secret stays on the server. The webhook helper cannot be bundled for the browser, by design.
Build from source
Section titled “Build from source”cd sdk-jsnpm installnpm run generate # regenerates src/generated from docs/api/openapi.jsonnpm run buildnpm test./verify.sh # the CI gate: drift, browser-bundle refusal, any-leaks, pack contentsnpm pack # produces binarybridges-bb-sign-sdk-0.1.0.tgzsrc/generated/ is generated and committed code: regenerate it rather than editing it.
GENERATION.md, in the SDK source, documents the generator configuration.