Skip to content
SignYu
TemplatesPricingAPIDocsAboutBlogContact
  1. Home
  2. API
  3. Integrations
  4. Java (Spring Boot)

Aadhaar eSign API in Java with Spring Boot

Java's built-in java.net.http.HttpClient has no multipart helper, so this guide includes a short, correct multipart body builder. The rest is a Spring Boot service for the API calls and a REST controller that verifies webhooks with javax.crypto.Mac and a constant-time comparison.

Updated 2026-10-01

What you need

  • Java 17 or newer and Spring Boot 3 (Jackson is on the classpath by default).
  • A SignYu API key (starts with sk_live_). API access costs ₹999/month with a 3-day free trial; create the key under Developers in the dashboard.
  • Signature credits for your signers, from ₹15 per signature (₹15 per signature on packs of 10 or more). Sending uses one credit per signer.
  • A webhook endpoint added under Developers, and its signing secret.

Step 1: Configuration

Bind the key and secret from application properties backed by environment variables.

signyu:
  api-key: sk_live_your_api_key        # set via SIGNYU_API_KEY in production
  webhook-secret: your_endpoint_secret # set via SIGNYU_WEBHOOK_SECRET

Step 2: Create a document with a hand-built multipart body

Each part needs its own headers, separated by CRLF. The file part must declare Content-Type: application/pdf.

@Service
public class SignYuClient {
    private static final String BASE = "https://signyu.com/api/v1";
    private final HttpClient http = HttpClient.newHttpClient();
    private final ObjectMapper json;
    private final String apiKey;

    public SignYuClient(ObjectMapper json, @Value("${signyu.api-key}") String apiKey) {
        this.json = json;
        this.apiKey = apiKey;
    }

    public String createDocument(Path pdf, String name) throws IOException, InterruptedException {
        String boundary = "signyu-" + UUID.randomUUID();
        String head = "--" + boundary + "\r\n"
            + "Content-Disposition: form-data; name=\"name\"\r\n\r\n" + name + "\r\n"
            + "--" + boundary + "\r\n"
            + "Content-Disposition: form-data; name=\"file\"; filename=\"" + pdf.getFileName() + "\"\r\n"
            + "Content-Type: application/pdf\r\n\r\n";
        String tail = "\r\n--" + boundary + "--\r\n";

        HttpRequest req = HttpRequest.newBuilder(URI.create(BASE + "/documents"))
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "multipart/form-data; boundary=" + boundary)
            .POST(HttpRequest.BodyPublishers.ofByteArrays(List.of(
                head.getBytes(StandardCharsets.UTF_8),
                Files.readAllBytes(pdf),
                tail.getBytes(StandardCharsets.UTF_8))))
            .build();

        return send(req).get("documentId").asText();
    }
}

Step 3: Add signers, send, and map errors

JSON calls share one send() method that turns non-2xx responses into a typed exception with the API error code.

public record Signer(String name, String phone, String email) {}

public static class SignYuException extends RuntimeException {
    public final int status;
    public final String code;
    SignYuException(int status, String code, String message) {
        super(message);
        this.status = status;
        this.code = code;
    }
}

public JsonNode addSignersAndSend(String documentId, List<Signer> signers) throws IOException, InterruptedException {
    String body = json.writeValueAsString(Map.of("signers", signers));
    send(jsonPost("/documents/" + documentId + "/signers", body));
    return send(jsonPost("/documents/" + documentId + "/send", ""));
}

private HttpRequest jsonPost(String path, String body) {
    return HttpRequest.newBuilder(URI.create(BASE + path))
        .header("Authorization", "Bearer " + apiKey)
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(body))
        .build();
}

private JsonNode send(HttpRequest req) throws IOException, InterruptedException {
    HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
    JsonNode node = json.readTree(res.body().isEmpty() ? "{}" : res.body());
    if (res.statusCode() >= 300) {
        // 402 insufficient_credits, 409 invalid_state, 401 unauthorized ...
        throw new SignYuException(res.statusCode(), node.path("error").asText(), node.path("message").asText());
    }
    return node;
}

Step 4: Download the signed PDF

Fetch downloadUrl with a request that has no Authorization header and stream it to disk.

JsonNode doc = send(HttpRequest.newBuilder(URI.create(BASE + "/documents/" + documentId))
    .header("Authorization", "Bearer " + apiKey).GET().build());

if ("COMPLETED".equals(doc.path("status").asText())) {
    HttpRequest download = HttpRequest.newBuilder(URI.create(doc.path("downloadUrl").asText())).GET().build();
    http.send(download, HttpResponse.BodyHandlers.ofFile(Path.of("signed", documentId + ".pdf")));
}

Verify webhooks

Take the body as @RequestBody String so Spring does not bind it to an object first. For application/json without a charset, Spring decodes it as UTF-8, so getBytes(UTF_8) gives back the original bytes; if you want zero doubt, take @RequestBody byte[] instead. Compare with MessageDigest.isEqual, which is constant time.

@RestController
public class SignYuWebhookController {
    private final byte[] secret;
    private final ObjectMapper json;
    private final ApplicationEventPublisher events;

    public SignYuWebhookController(@Value("${signyu.webhook-secret}") String secret,
                                   ObjectMapper json, ApplicationEventPublisher events) {
        this.secret = secret.getBytes(StandardCharsets.UTF_8);
        this.json = json;
        this.events = events;
    }

    @PostMapping(path = "/webhooks/signyu", consumes = "application/json")
    public ResponseEntity<Void> receive(@RequestBody String body,
                                        @RequestHeader(name = "X-SignSetu-Signature", defaultValue = "") String signature)
            throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret, "HmacSHA256"));
        String expected = "sha256=" + HexFormat.of().formatHex(mac.doFinal(body.getBytes(StandardCharsets.UTF_8)));

        if (!MessageDigest.isEqual(expected.getBytes(StandardCharsets.US_ASCII),
                                   signature.getBytes(StandardCharsets.US_ASCII))) {
            return ResponseEntity.badRequest().build();
        }

        JsonNode event = json.readTree(body);
        events.publishEvent(new SignYuEvent(event.path("event").asText(), event.path("documentId").asText()));
        return ResponseEntity.ok().build();
    }
}

Common mistakes

  • Binding the webhook to a DTO with @RequestBody MyEvent and re-serializing it never matches the signature. Verify the String or byte[] first, then parse.
  • Spring Security's CSRF protection blocks the webhook POST with 403. Exclude /webhooks/signyu from CSRF and permit it without login.
  • A missing CRLF or a stray space in the multipart builder gives a 400 invalid_request. Copy the boundary layout exactly.
  • Do not log the full Authorization header; mask the key in HTTP client logging.
  • Send is one way. A second POST to /send returns 409 invalid_state, and signers cannot be added after sending. Add every signer (up to 6) before you call send.

Frequently asked questions

Can I use RestClient or WebClient instead of HttpClient?

Yes. With RestClient or WebClient you can use MultipartBodyBuilder, which builds the parts for you; set the part's content type to application/pdf.

Does this work on Java 11?

HttpClient is available from Java 11. Replace HexFormat (Java 17) with a manual hex encoder and records with plain classes.

How do I process webhooks asynchronously in Spring?

Publish an application event, as above, and handle it in an @Async @EventListener or put it on a message queue. Return 200 quickly so delivery does not time out.

Is there an official Java SDK?

Not yet. The REST API is small, and the client class in this guide covers create, add signers, send and get.

API reference

  • Documents
  • Webhooks
  • Errors
  • Authentication

Other integration guides

  • Aadhaar eSign API in Node.js with Express
  • Aadhaar eSign API in C# and ASP.NET Core
  • Aadhaar eSign from Zoho Creator with Deluge

Get your API key

Start a 3-day free trial of API access and send your first document today.

Start free trialSee API pricing and features
SignYu

Pay-per-use Aadhaar eSign for Indian businesses, landlords, and individuals. Sign PDFs in 2 minutes at ₹15 per signature.

LinkedIn →

Product

  • Aadhaar eSign
  • Pricing
  • Templates
  • Rent Agreement eSign
  • Verify Signature
  • eSign Quiz
  • API
  • API Docs

Company

  • About
  • eSign Guide
  • Blog
  • Press
  • FAQ
  • Contact
Powered by eMudhra (CCA-licensed ESP)·IT Act 2000 Compliant·Aadhaar OTP Authenticated·Made in India 🇮🇳

© 2026 BN Habitat Pvt Ltd·CIN: U45400CH2010PTC043443·GST: 03AAECB5185C1Z3

Regd. Office: H.NO. 3355, 2nd Floor, Sector 37-D, Chandigarh, Chandigarh - 160036

Op. Office: Office 34, 13th Floor, Sushma Infinium, Chandigarh Ambala Expressway, Zirakpur, Punjab - 140603

TermsPrivacyRefundCookie