Java SDK
The Java SDK connects your services to the bb-sign API: it manages tokens, retries safely and
verifies webhook signatures. The coordinates are
co.binarybridges.sign:bb-sign-sdk-java:0.1.0-SNAPSHOT.
This page merges the SDK’s README.md and its README-INSTALL.md.
Requires Java 17 or newer.
Installation
Section titled “Installation”The SDK is delivered as four files: the jar, its pom, a .sha256 and the installation guide. You
can also build it from source (see Build from source). Install the jar into
your local Maven repository and declare it as a dependency.
-
Verify the integrity of the file you received:
Ventana de terminal shasum -a 256 -c bb-sign-sdk-java-0.1.0-SNAPSHOT.jar.sha256 -
Install it.
-DpomFileis required:Ventana de terminal mvn install:install-file \-Dfile=bb-sign-sdk-java-0.1.0-SNAPSHOT.jar \-DpomFile=bb-sign-sdk-java-0.1.0-SNAPSHOT.pom -
Declare it as a dependency:
<dependency><groupId>co.binarybridges.sign</groupId><artifactId>bb-sign-sdk-java</artifactId><version>0.1.0-SNAPSHOT</version></dependency>
Dependencies
Section titled “Dependencies”The build must have access to Maven Central. The SDK is a plain jar, not a shaded one: your build resolves okhttp, Retrofit and Jackson and aligns them with the versions you already use. This avoids conflicts with Jackson, which is present in most Java services.
If your build has no access to Maven Central, contact us before you start the integration to receive a shaded variant of the SDK.
Quickstart
Section titled “Quickstart”The complete example fits in one file. Credentials are created in bb-sign under Settings, tab API credentials; the secret is shown once, at creation.
import co.binarybridges.sign.sdk.BbSignClient;import co.binarybridges.sign.sdk.generated.model.*;import java.time.OffsetDateTime;import java.util.List;
try (BbSignClient client = BbSignClient.builder() .baseUrl(System.getenv("BBSIGN_BASE_URL")) .tokenUrl(System.getenv("BBSIGN_TOKEN_URL")) .clientCredentials(System.getenv("BBSIGN_CLIENT_ID"), System.getenv("BBSIGN_CLIENT_SECRET")) .build()) {
CreateEnvelopeRequest request = new CreateEnvelopeRequest() .title("Q3 Agreement") .otpRequired(false) .expiresAt(OffsetDateTime.now().plusHours(72)) .signers(List.of(new CreateSignerSpec() .email("signer@example.com") .name("A Signer") .signOrder(1)));
EnvelopeResponse envelope = client.execute(client.envelopes().createEnvelope(request)); System.out.println("Created " + envelope.getId());}Read the credentials from environment variables, outside your source code. The secret is stored
only in Keycloak and bb-sign keeps no copy; if it is lost, rotate it, which invalidates the old one
immediately. The client exposes envelopes(), documents() and verification().
List by page
Section titled “List by page”client.eachPage( page -> client.envelopes().listEnvelopes(page, 50, null, null, null, null, null, null), PagedEnvelopeResponse::getContent) .forEach(envelope -> System.out.println(envelope.getTitle()));Pages are fetched on demand as you iterate, so going through thousands of envelopes does not load them all into memory.
Verify a webhook
Section titled “Verify a webhook”Webhooks.verify checks the signature over the body exactly as received. Declare the body as
byte[] in your handler:
import co.binarybridges.sign.sdk.Webhooks;
@PostMapping("/webhooks/bb-sign")public ResponseEntity<Void> receive( @RequestHeader("X-BBSign-Signature") String signature, @RequestBody byte[] rawBody) { // byte[], NOT a DTO
if (!Webhooks.verify(signature, rawBody, System.getenv("BBSIGN_WEBHOOK_SECRET"))) { return ResponseEntity.status(401).build(); }
WebhookEvent event = new ObjectMapper().readValue(rawBody, WebhookEvent.class); switch (event.getType()) { case "envelope.completed" -> onCompleted(event.getData().getEnvelope()); case "signer.signed" -> onSigned(event.getData().getSigner()); default -> { /* ignore unknown types: more will be added */ } } return ResponseEntity.ok().build();}Capture the raw body
Section titled “Capture the raw body”The signature covers the exact bytes received. If the parameter is declared as a DTO, Spring parses the JSON and hands you an object; re-serializing it produces different bytes (key order and whitespace change) and verification fails even though the object is correct.
- Spring MVC:
@RequestBody byte[]or@RequestBody String, or aContentCachingRequestWrapperif you also need the parsed form. - Jakarta Servlet: read
request.getInputStream()once, before any other component.
The SDK takes byte[] rather than String deliberately: the type guides you to the correct use.
Respond immediately, process afterwards
Section titled “Respond immediately, process afterwards”Return 2xx as soon as the signature is valid and process the event asynchronously. bb-sign retries on a schedule spanning roughly 15 hours, and after 3 consecutive exhausted schedules it disables the endpoint; asynchronous processing keeps the response fast under load.
Deduplicate on the event id
Section titled “Deduplicate on the event id”X-BBSign-Event-Id is stable across retries and redeliveries of the same event. Delivery is
at-least-once, so an event can arrive twice; use the id as an idempotency key.
Secret rotation
Section titled “Secret rotation”While a rotation window is open, the header carries two v1= values. Webhooks.verify accepts
either, which lets you deploy the new secret without downtime and 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 in a unit test stops being valid. Use the clock-bearing overload:
Webhooks.verify(signature, rawBody, secret, Duration.ofSeconds(300), Instant.ofEpochSecond(1760000000L));Retries
Section titled “Retries”GET requests are retried on 5xx errors and connect timeouts, 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.
If a create times out, list before you retry:
try { client.execute(client.envelopes().createEnvelope(request));} catch (BbSignException e) { if (e.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 non-2xx response throws BbSignException, which exposes statusCode(). 0 indicates the
request got no answer.
| Status | Meaning |
|---|---|
| 400 | Malformed request or failed validation |
| 401 | Credentials rejected. The SDK refreshes the token 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, e.g. sending an already-sent envelope |
| 429 | An organization quota is exhausted |
Exception messages never include your secret or access token, even when bb-sign’s response contains them. Errors includes the full table and the error body.
Security guarantees
Section titled “Security guarantees”- Credentials stay out of logs. The SDK includes no logging interceptor and no hook to install
one. Avoid adding okhttp’s
HttpLoggingInterceptoratBODYyourself: it prints theAuthorizationheader and the token form body verbatim. - TLS is always verified. Certificate verification cannot be disabled.
- No duplicate writes. A write with an unknown outcome is not retried (see Retries).
Build from source
Section titled “Build from source”cd sdk-javamvn verify # regenerates the client, compiles, runs the tests./verify.sh # the CI gate: drift, Jackson-not-Gson, plugin survivalmvn package # writes the handover artifact to target/handover/The client under src/main/java/co/binarybridges/sign/sdk/generated/ is generated and
committed code: regenerate it rather than editing it. It is committed so that every spec change
shows up as a reviewable diff.