Skip to content
Developers

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.

The SDK is delivered as the tarball binarybridges-bb-sign-sdk-0.1.0.tgz, produced with npm pack. Install it in your project:

Ventana de terminal
npm install ../path/to/binarybridges-bb-sign-sdk-0.1.0.tgz

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.

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.

import { verifyWebhookSignature } from '@binarybridges/bb-sign-sdk/webhooks'

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.

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.

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.

X-BBSign-Event-Id is stable across retries and redeliveries. Delivery is at-least-once; use this value as an idempotency key.

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.

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.

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.

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.

  • 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.
Ventana de terminal
cd sdk-js
npm install
npm run generate # regenerates src/generated from docs/api/openapi.json
npm run build
npm test
./verify.sh # the CI gate: drift, browser-bundle refusal, any-leaks, pack contents
npm pack # produces binarybridges-bb-sign-sdk-0.1.0.tgz

src/generated/ is generated and committed code: regenerate it rather than editing it. GENERATION.md, in the SDK source, documents the generator configuration.