Skip to content
Developers

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.

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.

  1. 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
  2. Install it. -DpomFile is 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
  3. Declare it as a dependency:

    <dependency>
    <groupId>co.binarybridges.sign</groupId>
    <artifactId>bb-sign-sdk-java</artifactId>
    <version>0.1.0-SNAPSHOT</version>
    </dependency>

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.

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().

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.

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

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 a ContentCachingRequestWrapper if 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.

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.

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.

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.

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

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.

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.

  • Credentials stay out of logs. The SDK includes no logging interceptor and no hook to install one. Avoid adding okhttp’s HttpLoggingInterceptor at BODY yourself: it prints the Authorization header 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).
Ventana de terminal
cd sdk-java
mvn verify # regenerates the client, compiles, runs the tests
./verify.sh # the CI gate: drift, Jackson-not-Gson, plugin survival
mvn 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.