Secure 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.

Once you generate your license key, it will automatically be included in your sample code below.

License Key

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

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales