---
title: "Sign documents in a batch"
description: "How to sign several Signer documents with the user's certificate in one go from your own application: Signer's public signature calls, run once per document, with Web PKI signing every hash after a single confirmation."
canonical: "https://docs.lacunasoftware.com/en-us/articles/signer/integration/batch-signing"
locale: "en"
product: "Signer"
last_updated: "2026-10-12"
---

# Sign documents in a batch

Your application can have a user sign several Signer documents with their certificate, confirming only once. Signer has no batch
signature endpoint: your application runs Signer's *public signature* calls, start and complete, once per document, while
[Web PKI](https://docs.lacunasoftware.com/en-us/articles/web-pki) signs every hash in the browser after a single confirmation. 

> **Warning: Not in the API reference**
>
> The public signature endpoints on this page are not in Signer's API reference (Swagger). Lacuna's samples describe them as supported for
> client integrations; the only source for batch signing is the Java sample
> [`batch-pades-signature-signer`](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/BatchSignerSignatureController.java).

## Before you start

* Your server calls Signer with an API key, as in the rest of the [Integration Guide](https://docs.lacunasoftware.com/en-us/articles/signer/integration-guide). 
* Your page loads Web PKI to list the user's certificates and sign the hashes. 
* The certificate must belong to the participant: Signer checks that it has the participant's CPF, and fails with `CpfMismatch`
  otherwise. The sample creates each document with the CPF read from the certificate the user selected. 

## Flow

1. **Server**: for each document, get the participant's action URL and take from it the document's **key** and **ticket**.
1. **Browser**: Web PKI reads the user's certificate and asks them to authorize all the signatures at once.
1. **Server**: for each document, start the signature with the key, the ticket and the certificate; Signer returns the hash to sign and a
   token.
1. **Browser**: Web PKI signs the hash.
1. **Server**: complete the signature with the key, the signature and the token.

## 1. Get each document's key and ticket

The documents must already exist on Signer, with the user as a signer in their flow (see [Create documents](https://docs.lacunasoftware.com/en-us/articles/signer/integration/documents)); the
sample creates ten, only for the demonstration. 
For each document, the sample calls `POST /api/documents/{id}/action-url` with the participant's `emailAddress` and `identifier`, then
extracts the key and the ticket from the `embedUrl` it returns, whose shape is `…/document/key/{key}/sign-embedded?ticket={ticket}`: 

```java
private static final Pattern KEY_PATTERN = Pattern.compile("/key/([^/?]+)");
private static final Pattern TICKET_PATTERN = Pattern.compile("[?&]ticket=([^&]+)");
```

```java
List<CreateDocumentResult> results = signerClient.createDocument(documentRequest);
UUID documentId = results.get(0).getDocumentId();

ActionUrlRequest actionUrlRequest = new ActionUrlRequest();
actionUrlRequest.setEmailAddress(SIGNER_EMAIL);
actionUrlRequest.setIdentifier(cpf);
ActionUrlResponse actionUrlResponse = signerClient.getActionUrl(documentId, actionUrlRequest);

String embedUrl = actionUrlResponse.getEmbedUrl();
String key = extractGroup(KEY_PATTERN, embedUrl, "key");
String ticket = extractGroup(TICKET_PATTERN, embedUrl, "ticket");
```

The key goes in the URL of the start and complete calls; the ticket authorizes the signature and goes in the start request. Keep the
ticket on the server, associated with the key: the sample stores it in the HTTP session. 

* The key and ticket format is stated only in the samples' comments, not in the API reference. 
* To create many documents, the API reference recommends several requests (for 100 documents, 10 requests of 10), with
  `disablePendingActionNotifications` set, then `POST /api/users/notify-pending` once they are all created. 

## 2. Authorize all the signatures at once

In the browser, the sample reads the selected certificate and calls Web PKI's `preauthorizeSignatures` with the number of documents, so
that the user sees a single confirmation dialog: 

```js
pki.readCertificate(selectedCertThumbprint).success(function (certEncoded) {
	selectedCertContent = certEncoded;
	pki.preauthorizeSignatures({
		certificateThumbprint: selectedCertThumbprint,
		signatureCount: formElements.documentsIds.length
	}).success(startBatch); // Callback to be called if the user authorizes the signatures.
});
```

## 3. Start, sign and complete each signature

**Start.** Your server sends the certificate and the ticket to `POST /api/documents/keys/{key}/signature/certificate`. The response has
`toSignHash`, `digestAlgorithm` and `token`, along with `success` and `validationResults`: 

```java
String ticket = StorageMock.getSignerTicket(session, request.getId());
PublicStartSignatureRequest startRequest = new PublicStartSignatureRequest();
startRequest.setCertificate(Base64.getDecoder().decode(request.getCertContent()));
startRequest.setTicket(ticket);
StartSignatureResponse result = signerClient.startPublicSignature(request.getId(), startRequest);
```

**Sign.** The browser signs the hash with Web PKI's `signHash`: 

```js
pki.signHash({
	thumbprint: formElements.certificateSelect.val(),
	hash: step.toSignHash,
	digestAlgorithm: step.digestAlgorithm
}).success(function (signature) {
	step.signature = signature;
	done(step);
}).error(function (error) {
	renderFail(step, error);
	done();
});
```

**Complete.** Your server sends the signature and the token to `POST /api/documents/keys/{key}/signature`; the response has `success`
and `validationResults`: 

```java
CompleteSignatureRequest completeRequest = new CompleteSignatureRequest();
completeRequest.setSignature(Base64.getDecoder().decode(request.getSignature()));
completeRequest.setToken(request.getToken());
signerClient.completePublicSignature(request.getId(), completeRequest);
```

The certificate, the hash and the signature travel as Base64. 
The sample runs the three steps as a pipeline of queues, two documents at a time in each step. A document that fails leaves the pipeline
and the others go on, with no retry: 

```js
startQueue.process(startSignature, {threads: 2, output: performQueue});
performQueue.process(performSignature, {threads: 2, output: completeQueue});
completeQueue.process(completeSignature, {threads: 2, completed: onBatchCompleted});
```

Once completed, download each signed document from your server, for example with `GET /api/documents/{id}/content?type=Signatures`, so
that the API key never reaches the browser. See [Download and list documents](https://docs.lacunasoftware.com/en-us/articles/signer/integration/download-and-list). 

## What the sources don't say yet

* TODO(docs): the digest algorithm format: the Java sample says Signer returns an OID (such as `2.16.840.1.101.3.4.2.1`) and converts it
  to the name Web PKI expects (such as `SHA-256`), while the .NET public signature sample expects a name such as `SHA256`. 
* TODO(docs): limits on the number of documents per batch and rate limits for the signature calls; the sample only says to tune its
  concurrency to your Signer plan. 
* TODO(docs): how long the ticket and the token stay valid, and whether a complete call can be retried.
* TODO(docs): the error codes of the start and complete calls, and what `success` and `validationResults` mean.
* TODO(docs): whether the `keys/{key}` calls need the `X-Api-Key` header; the Java client always sends it. 

## See also

* [Create documents](https://docs.lacunasoftware.com/en-us/articles/signer/integration/documents)
* [Embedded signature](https://docs.lacunasoftware.com/en-us/articles/signer/embedded-signature), to sign one document at a time inside your application
* [Signer quick reference](https://docs.lacunasoftware.com/en-us/articles/signer/quick-reference)

**Demos and samples**

- Sign multiple Signer documents with Web PKI: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/BatchSignerSignatureController.java)
