Como Integrar Assinatura Digital em Java / Spring

Se você desenvolve em Java e precisa adicionar assinatura digital com validade jurídica a uma aplicação Spring Boot, este guia é o caminho mais curto entre o seu pom.xml e um documento assinado. Vamos do zero ao fim: instalar o SDK oficial via Maven ou Gradle, autenticar com OAuth2, criar uma sessão de assinatura, adicionar signatários, enviar o documento, receber o webhook de conclusão com verificação HMAC em um @RestController e, por fim, baixar o PDF assinado.

O foco aqui é prático e idiomático. Todos os trechos de código usam padrões que você já conhece do ecossistema Spring — injeção de dependência, WebClient, RestTemplate, records, @RestController — e podem ser colados quase diretamente no seu projeto, ajustando apenas credenciais e nomes de pacote.

Para um panorama mais amplo de capacidades, autenticação e endpoints, vale conferir a documentação da API de assinatura digital da SignDocs. Se você ainda está decidindo a arquitetura geral da integração, comece por o que é uma API de assinatura digital. Aqui, vamos direto ao código Java.

Pré-requisitos para a integração em Java

Antes de escrever a primeira linha de código, garanta que seu ambiente atende ao básico:

  • Java 11 ou superior (recomendado Java 17 LTS ou 21 LTS). O SDK oficial é compilado para o bytecode de Java 11.
  • Maven 3.6+ ou Gradle 7+ como gerenciador de build.
  • Spring Boot 2.7+ ou 3.x caso vá usar o exemplo de @RestController para webhooks (o SDK em si não depende de Spring).
  • Credenciais de API: um par client_id e client_secret obtidos no painel da SignDocs. Veja como obter sua API key de assinatura.
  • Um endpoint HTTPS público para receber webhooks (em desenvolvimento, use um túnel como o ngrok).
Dica: Comece sempre pelo ambiente de homologação. Aponte o cliente para api-hml.signdocs.com.br (com hífen, não api.hml) e use credenciais de sandbox. Lembre-se de que entidades de homologação têm TTL de 7 dias. Detalhes em ambiente de homologação e sandbox.

Passo 1 — Adicionar a dependência do SDK Java

A SignDocs mantém SDKs oficiais para TypeScript/Node, Python, Go, Java, PHP e C#/.NET. O SDK Java é publicado no Maven Central, então adicioná-lo é uma questão de uma dependência. Para usuários do Maven, inclua o seguinte bloco no seu pom.xml:

<!-- pom.xml --> <dependency> <groupId>io.github.signdocsbrasil</groupId> <artifactId>signdocsbrasil-api</artifactId> <version>1.2.0</version> </dependency>

Se você usa Gradle, basta uma linha no bloco dependencies do build.gradle:

// build.gradle (Groovy DSL) dependencies { implementation 'io.github.signdocsbrasil:signdocsbrasil-api:1.2.0' } // build.gradle.kts (Kotlin DSL) dependencies { implementation("io.github.signdocsbrasil:signdocsbrasil-api:1.2.0") }
Sobre a versão: confira o Maven Central para a versão mais recente de io.github.signdocsbrasil:signdocsbrasil-api antes de fixar a dependência. Recomendamos fixar uma versão exata em produção e atualizá-la de forma controlada. A única dependência transitiva é o Gson.

Após sincronizar o projeto (mvn install ou gradle build), as classes do cliente ficam disponíveis para importação. Não quer usar o SDK? Sem problema: a API REST é agnóstica de linguagem e pode ser consumida com WebClient, RestTemplate ou a java.net.http.HttpClient nativa — mostramos as duas abordagens ao longo do artigo.

Passo 2 — Autenticar com OAuth2 (client-credentials)

A API SignDocs usa OAuth2 com fluxo client-credentials. Você troca seu client_id e client_secret por um access_token do tipo bearer, assinado com ECDSA ES256 e válido por 15 minutos, e o envia no header Authorization de cada requisição. Para entender a fundo o fluxo, os escopos e a rotação de tokens, leia o guia de autenticação OAuth2 na API de assinatura.

Com o SDK oficial

O SDK abstrai todo o ciclo de vida do token: ele obtém, faz cache e renova o access_token automaticamente antes da expiração. Você apenas inicializa o cliente uma vez, idealmente como um @Bean gerenciado pelo Spring:

package com.suaempresa.signing.config; import com.signdocsbrasil.api.SignDocsBrasilClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SignDocsConfig { @Value("${signdocs.client-id}") private String clientId; @Value("${signdocs.client-secret}") private String clientSecret; @Value("${signdocs.base-url:https://api-hml.signdocs.com.br}") private String baseUrl; @Bean public SignDocsBrasilClient signDocsClient() { // O SDK obtém e renova o token OAuth2 automaticamente return SignDocsBrasilClient.builder() .clientId(clientId) .clientSecret(clientSecret) .baseUrl(baseUrl) .build(); } }

Mantenha o client-secret fora do código-fonte. Use application.yml com variáveis de ambiente, Spring Cloud Config, AWS Secrets Manager ou Vault:

# application.yml signdocs: client-id: ${SIGNDOCS_CLIENT_ID} client-secret: ${SIGNDOCS_CLIENT_SECRET} base-url: ${SIGNDOCS_BASE_URL:https://api-hml.signdocs.com.br} webhook-secret: ${SIGNDOCS_WEBHOOK_SECRET}

Sem SDK: obtendo o token manualmente com WebClient

Se preferir consumir a REST diretamente, o fluxo client-credentials cabe em poucas linhas com o WebClient do Spring WebFlux:

import org.springframework.web.reactive.function.client.WebClient; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; public String obterAccessToken(WebClient webClient, String clientId, String clientSecret) { MultiValueMap<String, String> form = new LinkedMultiValueMap<>(); form.add("grant_type", "client_credentials"); form.add("client_id", clientId); form.add("client_secret", clientSecret); TokenResponse token = webClient.post() .uri("https://api-hml.signdocs.com.br/oauth2/token") .header("Content-Type", "application/x-www-form-urlencoded") .bodyValue(form) .retrieve() .bodyToMono(TokenResponse.class) .block(); return token.accessToken(); } // Java record para a resposta do token public record TokenResponse( String accessToken, String tokenType, long expiresIn ) {}
Boa prática: faça cache do access_token em memória e só renove quando estiver perto de expirar (use o campo expires_in com uma margem de segurança de 60 segundos). Obter um token novo a cada requisição é desperdício e pode encostar nos rate limits. Para clientes enterprise em ambientes regulados (BACEN/Open Finance), o OAuth2 pode ser combinado com mTLS (mutual TLS) para autenticação mútua de ponta a ponta.

Escolhendo a superfície de API: Expressa ou Transacional

A SignDocs expõe duas superfícies de API, e a escolha define o restante da sua integração Java:

Superfície Quando usar Caracterização
Assinatura Expressa
(Signing Sessions)
Um signatário, fluxo rápido, checkout hospedado ou widget embutido Uma única chamada POST /v1/signing-sessions retorna a URL pronta de assinatura
API Transacional
(Envelopes)
Múltiplos signatários, ordem de assinatura, ciclo de vida completo Criação de envelope, adição de documentos e signatários, controle de estado granular

Neste tutorial usaremos a Assinatura Expressa como caminho principal por ser a forma mais rápida de colocar um documento para assinar em Java, e mostraremos a variação transacional onde for relevante. Para o desenho completo do ciclo de vida transacional, consulte o fluxo transacional da API.

Passo 3 — Criar uma sessão de assinatura e adicionar o signatário

Com o cliente autenticado, criar uma sessão de assinatura é uma única chamada. Você envia o documento (em base64 ou referência), define o signatário e o perfil de autenticação desejado. Veja o exemplo com o SDK Java, usando records imutáveis para o payload:

package com.suaempresa.signing.service; import com.signdocsbrasil.api.SignDocsBrasilClient; import com.signdocsbrasil.api.models.*; import org.springframework.stereotype.Service; import java.util.Base64; @Service public class AssinaturaService { private final SignDocsBrasilClient client; public AssinaturaService(SignDocsBrasilClient client) { this.client = client; } public SigningSession criarSessao(byte[] pdfBytes, String pdfNome) { // 1. Define o signatário — userExternalId é o ID dele no SEU sistema Signer signatario = new Signer("Maria Silva", "maria@empresa.com.br", "cliente-8821"); signatario.setCpf("12345678901"); // 2. Monta a requisição da sessão CreateSigningSessionRequest req = new CreateSigningSessionRequest(); req.setPurpose("DOCUMENT_SIGNATURE"); req.setPolicy(new Policy("DIGITAL_CERTIFICATE")); // ICP-Brasil (A1 via API) req.setSigner(signatario); CreateSigningSessionRequest.SessionDocument doc = new CreateSigningSessionRequest.SessionDocument( Base64.getEncoder().encodeToString(pdfBytes)); doc.setFilename(pdfNome); req.setDocument(doc); req.setReturnUrl("https://app.suaempresa.com.br/assinatura/ok"); // 3. Uma chamada cria a sessão e devolve a URL de assinatura return client.signingSessions().create(req); } }

A resposta traz a sessão criada, incluindo o identificador e a URL para a qual você redireciona (ou embute) o signatário:

// Resposta de POST /v1/signing-sessions { "sessionId": "01JC9K7M9P1R3T5V7X9Z1B3D5F", "transactionId": "01JC9K7M9P1R3T5V7X9Z1B3D5G", "status": "ACTIVE", "url": "https://sign-hml.signdocs.com.br/s/01JC9K7M9P1R3T5V7X9Z1B3D5F", "clientSecret": "ss_secret_xyz789...", "expiresAt": "2026-06-27T18:00:00Z" }
Atenção a dois detalhes importantes: (1) o valor de policy.profile é DIGITAL_CERTIFICATE para certificado ICP-Brasil — nunca DIGITAL_SIGN_A1, que é apenas um step.type na resposta; (2) a URL de compartilhamento real combina o campo url com o clientSecret como parâmetro de query ?cs=. O url sozinho não é o link de assinatura.

Perfis e métodos de autenticação disponíveis

O campo profile determina as etapas que o signatário percorre. As opções abrangem desde o clickwrap simples até biometria facial e certificado ICP-Brasil:

  • CLICK_ONLY — aceite por clique (clickwrap) com evidências.
  • CLICK_PLUS_OTP — aceite + código de uso único por SMS ou e-mail.
  • BIOMETRIC / BIOMETRIC_PLUS_OTP — prova de vida e match facial, com ou sem OTP.
  • DIGITAL_CERTIFICATE — assinatura com certificado ICP-Brasil A1 via API (o passo DIGITAL_SIGN_A1 aparece na resposta; titulares de token A3 usam o assinador desktop do aplicativo).

Para escolher a combinação certa por caso de uso e nível de garantia, leia a seção de métodos em o que é uma API de assinatura digital.

Variação transacional: envelope com múltiplos signatários

Quando o mesmo documento precisa de mais de um signatário e ordem de assinatura, use envelopes: crie o envelope com o modo sequencial e adicione uma sessão por signatário, com a posição definida pelo signerIndex:

// Envelope sequencial (múltiplos signatários, com ordem) CreateEnvelopeRequest envReq = new CreateEnvelopeRequest(); envReq.setSigningMode("SEQUENTIAL"); envReq.setTotalSigners(2); envReq.setDocumentContent(Base64.getEncoder().encodeToString(pdfBytes)); envReq.setDocumentFilename("contrato.pdf"); Envelope envelope = client.envelopes().create(envReq); AddEnvelopeSessionRequest s1 = new AddEnvelopeSessionRequest(); s1.setSignerName("Maria Silva"); s1.setSignerEmail("maria@empresa.com.br"); s1.setPolicyProfile("DIGITAL_CERTIFICATE"); s1.setSignerIndex(1); // assina primeiro EnvelopeSession sessao1 = client.envelopes().addSession(envelope.getEnvelopeId(), s1); AddEnvelopeSessionRequest s2 = new AddEnvelopeSessionRequest(); s2.setSignerName("João Souza"); s2.setSignerEmail("joao@empresa.com.br"); s2.setPolicyProfile("CLICK_PLUS_OTP"); s2.setSignerIndex(2); // só conclui depois do primeiro EnvelopeSession sessao2 = client.envelopes().addSession(envelope.getEnvelopeId(), s2); // Cada sessão tem sua própria URL de assinatura System.out.println(sessao1.getUrl() + " | " + sessao2.getUrl());

Passo 4 — Enviar o documento para assinatura

Na Assinatura Expressa, a própria criação da sessão já dispara o fluxo: você entrega a URL ao signatário (por e-mail, link no seu app ou checkout embutido). Em envelopes, cada addSession devolve a URL daquele signatário — e, se o envelope tiver um owner, a SignDocs envia os convites por e-mail automaticamente.

Há três formas comuns de entregar o link em uma aplicação Java/Spring:

  1. Redirecionamento direto: seu controller responde com um 302 para a URL da sessão (combinada com o ?cs=).
  2. Checkout hospedado: você simplesmente envia o link por e-mail e a SignDocs hospeda toda a experiência. Veja a página da Assinatura Expressa.
  3. Checkout embutido (popup): no frontend, o SDK @signdocs-brasil/js abre o checkout como popup a partir da sua UI, usando o clientSecret da sessão.

Um endpoint Spring que cria a sessão e devolve o link pronto ao frontend fica assim:

@RestController @RequestMapping("/api/assinaturas") public class AssinaturaController { private final AssinaturaService service; public AssinaturaController(AssinaturaService service) { this.service = service; } @PostMapping(consumes = "multipart/form-data") public ResponseEntity<LinkResponse> iniciar( @RequestParam("file") MultipartFile file) throws IOException { SigningSession sessao = service.criarSessao( file.getBytes(), file.getOriginalFilename()); // Monta o link de compartilhamento: url + ?cs=clientSecret String link = sessao.getUrl() + "?cs=" + sessao.getClientSecret(); return ResponseEntity.ok(new LinkResponse(sessao.getSessionId(), link)); } public record LinkResponse(String sessionId, String signingUrl) {} }
Segurança de fluxo: em add-ons e cenários onde o remetente não é o signatário, nunca redirecione o próprio remetente automaticamente para a URL de assinatura — isso permitiria que ele assinasse no lugar do signatário. Só faça auto-redirect quando o e-mail do remetente for igual ao do signatário.

Passo 5 — Receber o webhook com verificação HMAC em Spring

Em vez de ficar consultando o status (polling), você registra uma URL de webhook e a SignDocs envia um POST HTTPS sempre que algo acontece: SIGNING_SESSION.COMPLETED, TRANSACTION.COMPLETED, STEP.FAILED, etc. Os webhooks são assinados com HMAC-SHA256, têm idempotência e retentativa com backoff exponencial. O aprofundamento está em webhooks e eventos da API de assinatura.

O ponto crítico em Java é validar a assinatura HMAC usando o corpo bruto (raw body) da requisição. Se você desserializar o JSON antes de validar, a recodificação pode alterar bytes e quebrar o HMAC. Por isso, recebemos o corpo como String:

package com.suaempresa.signing.webhook; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.HexFormat; @RestController @RequestMapping("/webhooks/signdocs") public class WebhookController { private static final long TOLERANCIA_SEGUNDOS = 300; // 5 min @Value("${signdocs.webhook-secret}") private String webhookSecret; private final WebhookProcessor processor; public WebhookController(WebhookProcessor processor) { this.processor = processor; } @PostMapping public ResponseEntity<String> receber( @RequestBody String rawBody, @RequestHeader("X-SignDocs-Signature") String assinatura, @RequestHeader("X-SignDocs-Timestamp") String timestamp) { // 1. Verifica a assinatura HMAC sobre o corpo bruto if (!assinaturaValida(rawBody, assinatura, timestamp)) { return ResponseEntity.status(401).body("Assinatura inválida"); } // 2. Enfileira para processamento assíncrono e responde 200 processor.enfileirar(rawBody); return ResponseEntity.ok("received"); } private boolean assinaturaValida(String payload, String assinaturaRecebida, String timestamp) { try { // Protege contra replay attacks long agora = System.currentTimeMillis() / 1000; if (Math.abs(agora - Long.parseLong(timestamp)) > TOLERANCIA_SEGUNDOS) { return false; } // Monta a string assinada: timestamp + "." + payload String assinado = timestamp + "." + payload; Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec( webhookSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); byte[] hmac = mac.doFinal( assinado.getBytes(StandardCharsets.UTF_8)); String esperada = HexFormat.of().formatHex(hmac); // Comparação timing-safe contra timing attacks return MessageDigest.isEqual( esperada.getBytes(StandardCharsets.UTF_8), assinaturaRecebida.getBytes(StandardCharsets.UTF_8)); } catch (Exception e) { return false; } } }
Por que MessageDigest.isEqual? Comparar a assinatura com equals() ou == é vulnerável a timing attacks, em que um atacante infere caracteres corretos medindo o tempo de resposta. MessageDigest.isEqual em JDKs modernos faz comparação de tempo constante. Sempre valide a assinatura antes de desserializar ou processar qualquer dado do payload.

Se preferir não implementar o HMAC manualmente, o SDK traz o helper estático WebhookVerifier.verifySignature(body, signature, timestamp, secret), que valida a assinatura e a tolerância de replay em uma chamada.

Como webhooks operam com entrega at-least-once, seu processador deve ser idempotente. Use o id do payload como chave de deduplicação (em Redis, banco ou cache), garantindo que processar o mesmo evento duas vezes produza o mesmo resultado:

@Service public class WebhookProcessor { private final StringRedisTemplate redis; private final ObjectMapper mapper; public WebhookProcessor(StringRedisTemplate redis, ObjectMapper mapper) { this.redis = redis; this.mapper = mapper; } @Async public void enfileirar(String rawBody) throws Exception { JsonNode evento = mapper.readTree(rawBody); String eventId = evento.get("id").asText(); String tipo = evento.get("eventType").asText(); // SETNX: só processa se a chave ainda não existir (dedup) Boolean novo = redis.opsForValue().setIfAbsent( "webhook:" + eventId, "1", Duration.ofDays(7)); if (Boolean.FALSE.equals(novo)) { return; // evento duplicado, ignora } switch (tipo) { case "TRANSACTION.COMPLETED" -> baixarDocumentoAssinado(evento); case "STEP.COMPLETED" -> atualizarProgresso(evento); case "SIGNING_SESSION.CANCELLED" -> alertarCancelamento(evento); default -> { /* ignora eventos não tratados */ } } } }

Passo 6 — Baixar o PDF assinado e o pacote de evidências

Quando chega o evento TRANSACTION.COMPLETED, o documento final está pronto. Em Java, o método documents().download() devolve as URLs temporárias de download; você baixa os bytes e grava em disco, envia a um bucket S3 ou anexa a um e-mail:

public void baixarDocumentoAssinado(JsonNode evento) throws Exception { // o transactionId vem no topo do payload do webhook String transactionId = evento.get("transactionId").asText(); // 1. PDF assinado — a resposta traz a URL temporária (signedUrl) DownloadResponse download = client.documents().download(transactionId); try (InputStream in = URI.create(download.getSignedUrl()).toURL().openStream()) { Files.copy(in, Path.of("/dados/assinados/" + transactionId + ".pdf")); } // 2. Pacote de evidências .p7m: GET /v1/transactions/{id}/evidence // devolve os metadados e a URL temporária (downloadUrl) do arquivo JsonNode evidencia = mapper.readTree(httpGet( "/v1/transactions/" + transactionId + "/evidence")); try (InputStream in = URI.create( evidencia.get("downloadUrl").asText()).toURL().openStream()) { Files.copy(in, Path.of("/dados/evidencias/" + transactionId + ".p7m")); } }

Caso não esteja usando o SDK, o download é um simples GET autenticado com o bearer token, lido como byte[]:

// 1. Obter as URLs de download da transação DownloadUrls urls = webClient.get() .uri("https://api-hml.signdocs.com.br/v1/transactions/{id}/download", id) .header("Authorization", "Bearer " + accessToken) .retrieve() .bodyToMono(DownloadUrls.class) .block(); // 2. Baixar o PDF pela URL temporária retornada (signedUrl) byte[] pdf = webClient.get() .uri(URI.create(urls.signedUrl())) .retrieve() .bodyToMono(byte[].class) .block(); public record DownloadUrls(String signedUrl, String documentHash) {}

O PDF assinado segue o padrão PAdES (assinatura embarcada no PDF), enquanto o .p7m é um container PKCS#7/CMS que reúne as assinaturas, o hash SHA-256 do documento, o carimbo de hora do servidor e a trilha de auditoria. Para entender quando usar cada padrão, veja PKCS#7, CMS, PAdES e CAdES no glossário, e para o valor probatório do pacote, o evidence pack .p7m como prova jurídica.

Validação pública independente

Qualquer documento assinado pode ser conferido por terceiros, sem depender da sua aplicação, no verificador público da SignDocs. Isso reforça a confiança e a transparência do fluxo — útil em auditorias e disputas.

Visão de ponta a ponta e tratamento de erros

Reunindo tudo, o fluxo idiomático de uma integração Java/Spring com a SignDocs fica assim:

  1. Adicionar o SDK via Maven/Gradle (ou consumir a REST com WebClient).
  2. Configurar credenciais e o cliente OAuth2 como um @Bean.
  3. Criar a sessão de assinatura (Expressa) ou o envelope (Transacional) e adicionar signatários.
  4. Entregar o link de assinatura (redirect, e-mail ou checkout embutido via SDK).
  5. Receber o webhook em um @RestController, validar o HMAC e enfileirar de forma idempotente.
  6. Ao concluir, baixar o PDF PAdES e o pacote de evidências .p7m.

Em produção, trate erros de forma resiliente. A API usa convenções HTTP padrão; mapeie os principais cenários:

Status Significado Ação recomendada em Java
401 Token inválido ou expirado Renovar o access_token e repetir a chamada uma vez
400 Payload inválido (ex.: profile errado) Logar o corpo da resposta; não repetir sem corrigir
429 Rate limit excedido Respeitar Retry-After com backoff exponencial
5xx Erro temporário do servidor Retry com backoff; usar chave de idempotência
Cuidado com o profile: o erro 400 mais comum em integrações novas é enviar DIGITAL_SIGN_A1 como valor de policy.profile. Isso é rejeitado pela API. O valor correto para certificado ICP-Brasil é DIGITAL_CERTIFICATE; DIGITAL_SIGN_A1 aparece apenas como step.type na resposta.

Por que a SignDocs para projetos Java no Brasil

Diferente de soluções estrangeiras adaptadas, a SignDocs é nativa de ICP-Brasil, com produto e suporte em pt-BR e arquitetura LGPD-first. A validade jurídica se ancora na MP 2.200-2/2001, que institui a ICP-Brasil, e a plataforma cobre tanto a assinatura eletrônica avançada quanto a qualificada com certificado ICP-Brasil (A1 via API; A3 pelo assinador desktop do aplicativo).

  • SDK Java oficial publicado no Maven Central, ao lado de SDKs para Node, Python, Go, PHP e C#/.NET.
  • Webhooks robustos com HMAC-SHA256, idempotência e retry com backoff — fáceis de consumir em Spring.
  • mTLS opcional para clientes enterprise e regulados (BACEN/Open Finance).
  • Verificador público e pacote de evidências .p7m com força probatória.
  • Sandbox de homologação gratuito para validar a integração; o acesso de produção à API é um plano sob medida dimensionado com o time comercial — entenda os modelos em quanto custa uma API de assinatura.

Se você trabalha em outras stacks, há guias equivalentes para C#/.NET e PHP, todos seguindo o mesmo modelo de API.

Perguntas Frequentes

Existe um SDK oficial de assinatura digital para Java?

Sim. A SignDocs Brasil mantém SDKs oficiais para TypeScript/Node, Python, Go, Java, PHP e C#/.NET. O SDK Java é distribuído via Maven Central e funciona em qualquer projeto Java 11 ou superior, incluindo aplicações Spring Boot. Caso prefira não usar o SDK, a API REST é totalmente agnóstica de linguagem e pode ser consumida com qualquer cliente HTTP, como RestTemplate, WebClient ou a HttpClient nativa do Java.

Como adiciono a dependência do SDK Java via Maven e Gradle?

No Maven, adicione um bloco <dependency> com o groupId io.github.signdocsbrasil, artifactId signdocsbrasil-api e a versão desejada ao seu pom.xml. No Gradle, basta uma linha do tipo implementation 'io.github.signdocsbrasil:signdocsbrasil-api:1.2.0' no bloco dependencies do build.gradle. Após sincronizar o projeto, as classes do cliente ficam disponíveis para importação. Sempre confira a versão mais recente publicada no Maven Central.

Como funciona a autenticação OAuth2 no SDK Java?

A API usa OAuth2 com fluxo client-credentials. Você envia seu client_id e client_secret ao endpoint de token e recebe um access_token (bearer) assinado com ECDSA ES256, válido por 15 minutos. O SDK Java cuida automaticamente da obtenção, cache e renovação do token antes da expiração, mas você também pode implementar o fluxo manualmente com WebClient. Em integrações enterprise reguladas, é possível combinar OAuth2 com mTLS para autenticação mútua.

Como valido a assinatura HMAC de um webhook em um @RestController?

Receba o corpo bruto (raw body) do webhook como String, recalcule o HMAC-SHA256 usando seu webhook secret e compare com o header de assinatura enviado pela SignDocs. Use uma comparação timing-safe como MessageDigest.isEqual para evitar timing attacks e valide também o header de timestamp para prevenir ataques de replay. Só processe o evento depois que a assinatura for confirmada como válida; caso contrário, retorne HTTP 401.

Preciso de um certificado ICP-Brasil no servidor para usar a API em Java?

Não para o caso mais comum. A assinatura eletrônica avançada (clickwrap, OTP, biometria) não exige certificado no seu servidor. O certificado ICP-Brasil entra apenas quando você usa o perfil DIGITAL_CERTIFICATE (o passo DIGITAL_SIGN_A1 aparece na resposta), e nesse caso é o próprio signatário quem aplica o certificado A1 — a chave privada nunca passa pela sua aplicação Java. Seu backend apenas orquestra a transação via API.

Como baixo o PDF assinado e o pacote de evidências em Java?

Após receber o evento TRANSACTION.COMPLETED via webhook, chame documents().download(transactionId) no SDK Java. A resposta traz a URL temporária do PDF assinado (signedUrl); baixe os bytes e grave em disco, envie a um bucket S3 ou anexe a um e-mail. Além do PDF no padrão PAdES, você pode baixar o pacote de evidências .p7m (PKCS#7/CMS), que reúne a prova jurídica completa da assinatura.

Posso testar a integração Java em ambiente de homologação antes de ir para produção?

Sim. Aponte o cliente para o host de homologação api-hml.signdocs.com.br (com hífen, não api.hml) usando credenciais de sandbox. Esse ambiente replica o comportamento de produção sem custos nem efeitos legais reais. Lembre-se de que entidades criadas em homologação têm TTL de 7 dias, ou seja, são apagadas automaticamente após esse período — ideal para testes de CI/CD.

Coloque assinatura digital no seu app Java em minutos

SDK Java oficial no Maven Central, autenticação OAuth2, webhooks com HMAC-SHA256 e validade jurídica ICP-Brasil. O acesso à API é um plano sob medida, com sandbox de homologação gratuito para integrar antes de qualquer contrato.

Fale com o time comercial Conheça a plataforma grátis