Digital Signatures Showcase Demo Code Sample

Requirements
View Demo

Easily add secure digital signatures to your PDFs using cryptographic certificates. A digital signature acts like a unique fingerprint, verifying the sender’s identity and ensuring document authenticity.

This demo allows you to:

  • Add PFX certificate to the signature
  • Validate digital signature on the sample PDF
  • Download a digitally signed PDF

Implementation steps
To add Digital Signature capability with WebViewer:

Step 1: Get started with WebViewer in your preferred web stack
Step 2: Add the ES6 JavaScript sample code provided in this guide

1// ES6 Compliant Syntax
2// Copilot name: GitHub Copilot, version: 1.0.0, model: GPT-4, version: 2024-06, date: 2025-10-13
3// File: showcase-demos/digital-signatures/index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7function initializeWebViewer() {
8 WebViewer(
9 {
10 path: '/lib',
11 initialDoc: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/digital_signature_walkthrough.pdf',
12 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
13 fullAPI: true, // Enable the full API to access PDFNet and signature features.
14 licenseKey: 'YOUR_LICENSE_KEY', // Replace with your license key
15 },
16 document.getElementById('viewer')
17 ).then((instance) => {
18 const { UI } = instance;
19 const { documentViewer, Tools, Annotations, annotationManager } = instance.Core;
20
21 // Set the toolbar group to the Fill & Sign tools
22 UI.setToolbarGroup('toolbarGroup-FillAndSign');
23
24 documentViewer.addEventListener('documentLoaded', () => {
25 widgetsToDigitallySign = [];
26 });
27
28 UI.VerificationOptions.addTrustedCertificates([certificate]);
29
30 // Sets the Signature Tool to sign with appearance mode for use with digital signatures
31 const tool = documentViewer.getTool(Tools.ToolNames.SIGNATURE);
32 tool.setSigningMode(Tools.SignatureCreateTool.SigningModes.APPEARANCE);
33
34 // Customize the webviewer left panel
35 UIElements.customizeUI(instance);
36
37 UI.openElements([UIElements.tabPanel.dataElement]);
38 UI.setPanelWidth(UIElements.tabPanel.dataElement, 400);
39
40 // Capture the signature fields that are signed by the user
41 annotationManager.addEventListener('annotationChanged', async (annotations, action) => {
42 const actionsOfInterest = ['add', 'delete'];
43
44 if (actionsOfInterest.includes(action)) {
45 const signatureWidgetAnnots = annotationManager
46 .getAnnotationsList()
47 .filter((annot) => annot instanceof Annotations.SignatureWidgetAnnotation);
48
49 const widgetsWithSignatures = signatureWidgetAnnots.filter(
50 (widget) => widget.isSignedByAppearance() || widget.getAssociatedSignatureAnnotation()
51 );
52 // If signature field is signed, enable the apply approval button
53 const widgetsToSign = widgetsWithSignatures.map((widget) => {
54 if (widget.isSignedByAppearance()) {
55 const applyApprovalButton = UIElements.digitalSignaturePanel.render.querySelector('#applyApprovalButton');
56 applyApprovalButton.disabled = false;
57 applyApprovalButton.style.backgroundColor = 'blue';
58 applyApprovalButton.style.color = 'white';
59 }
60 return {
61 label: widget.getField().name,
62 };
63 });
64
65 widgetsToDigitallySign = widgetsToSign;
66 console.log('Annotation changed:', annotations, action, widgetsToSign);
67 }
68 });
69 console.log('WebViewer loaded successfully.');
70 }).catch((error) => {
71 console.error('Failed to initialize WebViewer:', error);
72 });
73}
74
75// Apply the digital signature approval
76window.applyApproval = async (instance) => {
77 const { UI } = instance;
78 const { annotationManager, SaveOptions, PDFNet, documentViewer } = instance.Core;
79 const xfdfString = await annotationManager.exportAnnotations();
80 const data = await documentViewer.getDocument().getFileData({
81 xfdfString,
82 flags: SaveOptions.INCREMENTAL,
83 });
84
85 await PDFNet.initialize();
86 await PDFNet.runWithCleanup(async () => {
87 const doc = await PDFNet.PDFDoc.createFromBuffer(new Uint8Array(data));
88 const digSigFieldIterator = await doc.getDigitalSignatureFieldIteratorBegin();
89 let foundOneDigitalSignature = false;
90 for (digSigFieldIterator; await digSigFieldIterator.hasNext(); digSigFieldIterator.next()) {
91 const field = await digSigFieldIterator.current();
92 if (await field.hasVisibleAppearance()) {
93 foundOneDigitalSignature = true;
94 break;
95 }
96 }
97 await doc.lock();
98
99 try {
100 /**
101 * Create a deep copy of widgetsToDigitallySign so that we can safely
102 * modify the contents of the array if needed (i.e. if we want to push
103 * a field to the array in the event that the document has no fields, or
104 * the user chose not to sign a signature field)
105 */
106 const widgetsToSign = JSON.parse(JSON.stringify(widgetsToDigitallySign));
107
108 /**
109 * If the user did not explicitly sign a field in the document,
110 * arbitrarily create an invisible signature field
111 */
112 if (!widgetsToSign.length) {
113 const fieldName = 'Signature1-invisible';
114 const field = await doc.fieldCreate(fieldName, PDFNet.Field.Type.e_signature);
115 const page1 = await doc.getPage(1);
116 const widgetAnnot = await PDFNet.WidgetAnnot.create(
117 await doc.getSDFDoc(),
118 await PDFNet.Rect.init(0, 0, 0, 0),
119 field
120 );
121 page1.annotPushBack(widgetAnnot);
122 widgetAnnot.setPage(page1);
123 const widgetObj = await widgetAnnot.getSDFObj();
124 widgetObj.putNumber('F', 132);
125 widgetObj.putName('Type', 'Annot');
126 widgetsToSign.push({
127 label: fieldName,
128 });
129 }
130
131 const visited = [];
132 let buf;
133
134 for (let i = 0; i < widgetsToSign.length; i++) {
135 let sigField;
136 const widgetFieldName = widgetsToSign[i].label;
137 const fieldIterator = await doc.getFieldIteratorBegin();
138 for (; await fieldIterator.hasNext(); fieldIterator.next()) {
139 const field = await fieldIterator.current();
140 if (
141 !(await field.isValid()) ||
142 (await field.getType()) !== PDFNet.Field.Type.e_signature
143 ) {
144 continue;
145 }
146 const fieldName = await field.getName();
147 if (!visited.includes(fieldName) && widgetFieldName === fieldName) {
148 visited.push(fieldName);
149 sigField = await PDFNet.DigitalSignatureField.createFromField(field);
150 break;
151 }
152 }
153 if (!sigField) {
154 /**
155 * A guard clause in-case a field with the given `label` could not
156 * be found, but this should never happen, as widgetInfo.label
157 * can only be programmatically populated from a user interacting
158 * with an existing field in the document
159 */
160 throw Error('The document does not contain a signature field');
161 }
162 if (!foundOneDigitalSignature) {
163 /**
164 * No Signature Field with a Cryptographic signature was found in
165 * the document, therefore we should explicitly set DocMDP
166 */
167 await sigField.setDocumentPermissions(
168 PDFNet.DigitalSignatureField.DocumentPermissions
169 .e_annotating_formfilling_signing_allowed
170 );
171 }
172 // Determine whether to sign with a digitalID (selected file) or the default certificate URL.
173 if (UIElements.certificateUrl !== null) {
174 if (UIElements.certificateUrl !== String.empty &&
175 UIElements.certificateUrl !== defaultCertificateUrl)
176 digitalID = UIElements.certificateUrl;
177 }
178 // Sign with a digitalID (selected file)
179 if (digitalID) {
180 const fileArrayBuffer = await digitalID.arrayBuffer();
181 await sigField.signOnNextSaveFromBuffer(fileArrayBuffer, UIElements.password);
182 }
183 // Sign with the default certificate URL
184 else
185 await sigField.signOnNextSaveFromURL(UIElements.certificateUrl, UIElements.password);
186
187 // Set optional signature information
188 await sigField.setLocation(UIElements.signatureInformation[0].value);
189 await sigField.setReason(UIElements.signatureInformation[1].value);
190 await sigField.setContactInfo(UIElements.signatureInformation[2].value);
191
192 buf = await doc.saveMemoryBuffer(PDFNet.SDFDoc.SaveOptions.e_incremental);
193 }
194 const blob = new Blob([buf], { type: 'application/pdf' });
195 UI.loadDocument(blob, { filename: documentViewer.getDocument().filename });
196 } catch (e) {
197 console.log(e);
198 UI.showWarningMessage({
199 title: 'Digital ID Error',
200 message:
201 'There is an issue with the Digital ID file or password. The private key could not be parsed.',
202 });
203 }
204 });
205};
206
207// Open the signature panel and verify the signature
208window.verifySignature = (instance) => {
209 instance.UI.setActiveTabInPanel({ tabPanel: UIElements.tabPanel.dataElement, tabName: 'signaturePanel' });
210 setTimeout(() => {
211 const shadowRoot = document.getElementById('wc-viewer').shadowRoot;
212 const signaturePanelElement = shadowRoot.querySelector('[data-element="signaturePanel"]');
213 const signaturePanelButtons = Array.from(signaturePanelElement.querySelectorAll('button'));
214 const signaturePanelExpandButton = signaturePanelButtons.find((element) => {
215 return element.ariaLabel.includes('Expand Signed by Apryse');
216 });
217 if (signaturePanelExpandButton)
218 signaturePanelExpandButton.click();
219 setTimeout(() => {
220 const verifyButton = signaturePanelElement.querySelector(
221 'button[aria-label="Signature Details"]'
222 );
223 if (verifyButton)
224 verifyButton.click();
225 }, 200);
226 }, 500);
227};
228
229// Clear the digital ID information
230window.clearDigitalIDInformation = (instance) => {
231 instance.UI.showWarningMessage({
232 title: 'Confirm Clearing Digital ID Information',
233 message:
234 'This will reset the inputted password and clear the uploaded .pfx file. Are you sure?',
235 onConfirm: () => {
236 UIElements.certificateUrl = defaultCertificateUrl;
237 UIElements.password = 'password';
238 digitalID = null;
239
240 // Reset the digital ID file name label and password field
241 const digitalIDFileNameLabel = UIElements.digitalSignaturePanel.render.querySelector('#digitalIDFileNameLabel');
242 digitalIDFileNameLabel.textContent = '';
243 const passwordField = UIElements.digitalSignaturePanel.render.querySelector('#inputPassword');
244 passwordField.value = UIElements.password;
245 passwordField.disabled = true;
246 }
247 });
248};
249
250//helper function to load the ui-elements.js script
251function loadUIElementsScript() {
252 return new Promise((resolve, reject) => {
253 if (window.UIElements) {
254 console.log('UIElements already loaded');
255 resolve();
256 return;
257 }
258 const script = document.createElement('script');
259 script.src = '/showcase-demos/digital-signatures/ui-elements.js';
260 script.onload = function () {
261 console.log('✅ UIElements script loaded successfully');
262 resolve();
263 };
264 script.onerror = function () {
265 console.error('Failed to load UIElements script');
266 reject(new Error('Failed to load ui-elements.js'));
267 };
268 document.head.appendChild(script);
269 });
270}
271
272// The url to the PKCS #12 private keyfile to use to certify this digital signature.
273const defaultCertificateUrl = '/assets/certificates/apryse.pfx';
274// The X.509 Public Key Certificates to be used for validating Digital Signatures on a document.
275const certificate = '/assets/certificates/apryse.cer';
276// The annotation widgets to sign
277let widgetsToDigitallySign = [];
278// The digital ID file (PKCS #12) selected by the user
279let digitalID = null;
280// Load UIElements script first, then initialize WebViewer
281loadUIElementsScript().then(() => {
282 initializeWebViewer();
283}).catch((error) => {
284 console.error('Failed to load UIElements:', error);
285});
286

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales