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 signs every hash in the browser after a single confirmation.
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.
Before you start
- Your server calls Signer with an API key, as in the rest of the 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
CpfMismatchotherwise. The sample creates each document with the CPF read from the certificate the user selected.
Flow
- Server: for each document, get the participant's action URL and take from it the document's key and ticket.
- Browser: Web PKI reads the user's certificate and asks them to authorize all the signatures at once.
- Server: for each document, start the signature with the key, the ticket and the certificate; Signer returns the hash to sign and a token.
- Browser: Web PKI signs the hash.
- 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); 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}:
private static final Pattern KEY_PATTERN = Pattern.compile("/key/([^/?]+)");
private static final Pattern TICKET_PATTERN = Pattern.compile("[?&]ticket=([^&]+)");
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
disablePendingActionNotificationsset, thenPOST /api/users/notify-pendingonce 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:
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:
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:
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:
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:
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.
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 asSHA-256), while the .NET public signature sample expects a name such asSHA256. - 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
successandvalidationResultsmean. - TODO(docs): whether the
keys/{key}calls need theX-Api-Keyheader; the Java client always sends it.
See also
- Create documents
- Embedded signature, to sign one document at a time inside your application
- Signer quick reference