How to Sign PDFs Using Digital Certificates in Node.js/JavaScript/TypeScript

Introduction

In this guide, we will walk you through the process of signing PDFs digitally using Node.js environment. We’ll be covering how to accomplish this using JavaScript or TypeScript. We’ll be utilizing a free external service API called pdfsignify.com, which simplifies the digital signing process and allows for easy personalization of signatures.
Digital signatures ensure the authenticity and integrity of your PDFs. To sign a PDF, you’ll need a digital certificate in the form of a .pfx or .p12 file, which contains your private key and certificate. Additionally, you'll need the password associated with your certificate file.

Part 1: Setting Up a Node.js Server

In this step, we’ll set up a new Node.js server that will generate and sign our PDFs. We’ll use Express for creating the server, Axios for making API requests to sign the PDFs, and fs (File System) to read the content of a PDF file.
If you already have your project set up and only need the server functionality, feel free to skip this part.

Step 1: Initialize Your Node.js Project

First, create a new directory for your project and navigate into it:
Copy
mkdir pdf-signing-server
cd pdf-signing-server
Next, initialize a new Node.js project:
Copy
npm init -y
This will create a package.json file.

Step 2: Install Required Packages

Now, install the required packages:
Copy
npm install express axios fs
  • Express: A minimal and flexible Node.js web application framework.
  • Axios: A promise-based HTTP client for making API requests.
  • fs: A built-in Node.js module for interacting with the file system.

Step 3: Create the Server

Create a new file called server.js (or server.ts if you're using TypeScript). In this file, we'll set up a basic Express server.
Copy
const express = require('express');
const fs = require('fs');
const axios = require('axios');

const app = express();
const port = 3000;

app.use(express.json());

// Basic route to test the server
app.get('/', (req, res) => {
  res.send('PDF Signing Server is up and running!');
});

// Example route for generating and signing a PDF (to be implemented)
app.post('/sign-pdf', async (req, res) => {
  // PDF generation and signing logic will go here
  res.send('PDF signed successfully!');
});

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});
This basic setup will get the server up and running. You can test it by running:
Copy
node server.js
Then, visit http://localhost:3000 in your browser to confirm that the server is working
Tutorial image description

Part 2: Setting Up PDF Signify

Now that our Node.js server is up and running, it's time to set up PDF Signify to sign our PDFs. PDF Signify is a free service that allows us to easily add digital signatures to our PDF files. In this section, we'll guide you through the process of creating an account and preparing the service for use.

Step 1: Create an Account or Log In

1- Visit the PDFSignify Website: Go to the PDFSignify website at pdfsignify.com.
Tutorial image description
2- Register for an Account or Login: If you don't have an account yet, click on the "Register" button.
You have the option to sign up using Google or Microsoft accounts for a quick registration process. If you prefer manual registration, fill in your details (email, password, etc.) and complete the registration process.
Tutorial image description

Step 2: Verify Your Email

After registering, you will need to verify your email address to activate your account:
1- Check Your Inbox: Shortly after registering, you'll receive a verification email from PDFSignify.
Tutorial image description
2- Click the Verification Button: Open the email and click on the provided verification button or link to confirm your email address.
Tutorial image description
3- Didn't Receive the Email?: If you don't receive the email within a few minutes, check your spam or junk folder. If it's still not there, return to the PDFSignify dashboard and request another verification email.
Tutorial image description

Step 3: Create a New Project in PDFSignify

Now that you have verified your email and gained access to the PDFSignify dashboard, it's time to create a new project to begin working with the service. This will allow you to generate API keys, track usage, and manage your digital signatures.
1- Choose a Plan: Upon creating a new project, you'll be prompted to select a pricing plan. For our purposes, we will use the Free Plan, which comes with limitations but is sufficient for testing and basic usage.
The Free Plan typically includes a limited number of API calls per month, so keep this in mind during development.
Tutorial image description
2- Project Setup: After selecting the pricing plan, click next and give a name to your project.
Tutorial image description

Step 4: Explore the Admin Dashboard

With your project created, you can now view the Admin Dashboard. This is where you'll find important information and controls related to your project:
1- API Usage: The dashboard will show details about your API usage, including the number of API calls you've made and your remaining quota.
2- Limits: ou can also view your plan's limitations, such as the maximum number of API requests and any other restrictions associated with the Free Plan.
3- API Keys: In the dashboard, you'll find your API keys, which is essential for authenticating your requests from the Node.js server. Copy this key and keep it safe, as you'll need it in the next steps.
Tutorial image description

Part 3: Create API Credentials

Now that your project is set up in PDF Signify, the next step is to create API credentials. These credentials are essential for authenticating your requests to the PDF Signify API from your Node.js server. Follow the steps below to create and manage your API credentials.

Step 1: Navigate to the API Credentials Section

Locate and click on the "API Credentials" option in the sidebar. This is where you can create and manage your API keys.
Tutorial image description

Step 2: Create New API Credentials

In the API Credentials section, you'll see a "Create Credentials" button. Click on this button to generate a new set of API credentials.
After clicking the button, a popup will appear showing your new API credentials. These include:
  • API Key:Used to authenticate your API requests.
  • Secret Key:Used to sign your requests securely
The secret key will only be shown once, so make sure to copy and save it in a secure location. If you lose it, you will need to regenerate a new set of credentials.
Tutorial image description

Part 4: Coding the Node.js Application

Now that we have our PDF Signify API credentials, it's time to integrate them into our Node.js application. For this example, we'll use an already created PDF to save time.
If you need to generate a PDF first, you can use a library like Puppeteer or pdf-lib. However, once the PDF is generated, you can directly send its array buffer to PDF Signify without saving and reading the file again.

Step 1: Save the Already Created PDF

Before we move on to signing the PDF, let's save the PDF file that you'll be working with. This file will be located in the root directory of your Node.js project.
1- Obtain the PDF File: Ensure you have the PDF file that you want to sign. If you've generated the PDF elsewhere, make sure it's ready.
2- Save the PDF in the Root Directory: Move or copy your PDF file to the root directory of your Node.js project (the same directory where your server.js file is located). Rename the file to filepdf.pdf.
For example, your project directory structure should look like this:
Copy
pdf-signing-server/
│
├── server.js
├── package.json
├── node_modules/
└── filepdf.pdf

Step 2: Add the dependencies

To ensure the necessary dependencies are imported at the top of your server.js file, you should add the following lines of code.
Copy
const fs = require('fs'); // Import fs module for file system operations
const axios = require('axios'); // Import Axios for making HTTP requests
const FormData = require('form-data'); // Import FormData for handling multipart form data
  • axios: This module is used for sending HTTP requests, which is crucial for interacting with the PDFSignify API.
  • fs: The file system module is necessary for reading your PDF files, digital certificates, and any images you might need.
  • FormData: This module allows you to construct multipart form data, which is needed to send the PDF and other files to the API.

Step 3: Create the signing route

To create a route in your Node.js application that signs a PDF and allows the client to download the signed PDF, you need to implement a new GET route in your server.js.
Here's how you can set up the GET route /sign-pdf.
Copy
app.get('/sign-pdf', async (req, res) => {
    console.error('Sign my pdf');
});

Step 4: Set Up Your Signing Certificate and Password

To sign PDFs using PDF Signify, you need a valid digital certificate and its associated password. This step involves placing your certificate in the project directory and configuring your application to use it.
Ensure you have your digital certificate file ready. This could be in .pfx or .p12 format.
Place your certificate file in the root directory of your Node.js project. Name it certificate.pfx or certificate.p12.
Your project directory should now include the certificate:
Copy
pdf-signing-server/
├── server.js
├── package.json
├── node_modules/
├── filepdf.pdf
└── certificate.pfx   // or certificate.p12

Step 5: Read the Necessary Files in the Code

To finalize the setup, you'll need to ensure that your code correctly reads the PDF and certificate files and securely manages the certificate password.
In your /sign-pdf route, read the PDF and certificate files using the fs module. Here's how to integrate these into your existing function adding this:
Copy
const cert = fs.readFileSync('certificate.pfx');
const pdf = fs.readFileSync('filepdf.pdf');
const password = 'YOUR_CERTIFICATE_PASSWORD';

Step 6: Create the Form Data to Prepare the Request

To send a multipart/form-data request for signing a PDF, you'll need to prepare the form data correctly. This involves creating a FormData object and appending the necessary fields, such as the certificate file, certificate password, and the PDF file to be signed.
Here's how you can implement this:
Copy
// Import FormData for constructing the form data (not needed in client-side JavaScript)
const formData = new FormData();

// Append the digital certificate to the form data
formData.append('certificate', cert, {
    filename: 'certificate.pfx',
    contentType: 'application/x-pkcs12',
});

// Append the password for the digital certificate
formData.append('certificatePassword', 'password');

// Append the PDF document to the form data
formData.append('pdf', pdf, {
    filename: 'filepdf.pdf',
    contentType: 'application/pdf',
});

Step 7: Execute the Request and Handle the Result

Now that we have set up the form data and included the necessary fields, it's time to execute the request to sign the PDF. We'll use axios to make the POST request to the PDFSignify API and handle the response.
Add this in the route:
Copy
try {
        // Make a POST request using axios with form data
        const response = await axios.post('https://api.pdfsignify.com/api/v1/sign-pdf', formData, {
            headers: {
                contentType: 'multipart/form-data', // Important: set content type to 'multipart/form-data'
                'AccessKey': 'MY_ACCESS_KEY', // Your access key for the API
                'SecretKey': 'MY_SECRET_KEY' // Your secret key for the API
            },
            responseType: 'arraybuffer' // The response will be in arrayBuffer format to handle the returned PDF
        });

        // Send the signed PDF as a response
        res.contentType('application/pdf');
        return res.send(response.data);
    } catch (error) {
        let errorResponse = error
        if(error?.response?.data){
            errorResponse = error.response.data.toString()
        }
        // Log any errors and return the error object
        console.error('error',errorResponse);
        res.send(errorResponse);
    }
IMPORTANT: In the request headers, replace MY_ACCESS_KEY with the access key you generated on the PDFSignify website, and replace MY_SECRET_KEY with the secret key generated in your credentials.
Now, you're server.js file will look like:
Copy
const express = require('express');
const fs = require('fs'); // Import fs module for file system operations
const axios = require('axios'); // Import Axios for making HTTP requests
const FormData = require('form-data'); // Import FormData for handling multipart form data

const app = express();
const port = 3000;

app.use(express.json());

// Basic route to test the server
app.get('/', (req, res) => {
  res.send('PDF Signing Server is up and running!');
});

app.get('/sign-pdf', async (req, res) => {
    // Read the digital certificate file (can be .pfx or .p12 format)
    const cert = fs.readFileSync('certificate.pfx');

    // Read the PDF document to be signed
    const pdf = fs.readFileSync('filepdf.pdf');

    // Import FormData for constructing the form data (not needed in client-side JavaScript)
const formData = new FormData();

// Append the digital certificate to the form data
formData.append('certificate', cert, {
    filename: 'certificate.pfx',
    contentType: 'application/x-pkcs12',
});

// Append the password for the digital certificate
formData.append('certificatePassword', 'password');

// Append the PDF document to the form data
formData.append('pdf', pdf, {
    filename: 'filepdf.pdf',
    contentType: 'application/pdf',
});

    // Send the request to sign the PDF document
    try {
        // Make a POST request using axios with form data
        const response = await axios.post('https://api.pdfsignify.com/api/v1/sign-pdf', formData, {
            headers: {
                contentType: 'multipart/form-data', // Important: set content type to 'multipart/form-data'
                'AccessKey': 'MY_ACCESS_KEY', // Your access key for the API
                'SecretKey': 'MY_SECRET_KEY' // Your secret key for the API
            },
            responseType: 'arraybuffer' // The response will be in arrayBuffer format to handle the returned PDF
        });

        // Send the signed PDF as a response
        res.contentType('application/pdf');
        return res.send(response.data);
    } catch (error) {
        let errorResponse = error
        if(error?.response?.data){
            errorResponse = error.response.data.toString()
        }
        // Log any errors and return the error object
        console.error('error',errorResponse);
        res.send(errorResponse);
    }
});

// Example route for generating and signing a PDF (to be implemented)
app.post('/sign-pdf', async (req, res) => {
  // PDF generation and signing logic will go here
  res.send('PDF signed successfully!');
});

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);

Step 8: Rerun the Server and Test the Sign Route

Now it's time to test your implementation.
Restart your server to apply the changes. Since this basic server setup does not include live reloading, you'll need to manually restart the server with the command node server.js
Open your web browser and navigate to the sign route by entering the following URL sign-pdf route http://localhost:3000/sign-pdf

Part 5: Check the results

Error Response

If you encounter an error, you will see a message indicating the issue and its cause. Common errors include:
1. PDF not saved with the correct name: Ensure the file name matches the required format.
2. Certificate not saved with the correct name: Verify both the file name and extension are correct.
3. Incorrect certificate password: Double-check that the password you entered is correct.
4. Incorrect API credentials: Review your API credentials and regenerate them if necessary.

Success Response

Once the route is opened, you will see the signed PDF. However, the signatures may not appear in the browser.
To view the signatures, download the PDF and open it with a PDF viewer on your operating system. On Windows and macOS, you can use Adobe Reader, while on Linux, you can use the default Document Viewer .
The signature will be visible at coordinates (0,0), which is located at the bottom left corner of the page.
Tutorial image description

Part 6: Customize the Signature

Pdfsignify offers a wide range of features to customize your signature. In this section, we'll explore many of the available customization options.
For a complete list of features and options, you can refer to the documentation here.

Step 1: Background Image Customization:

To customize the background image, a payment account is required. Once you have a payment account, you can set your logo as the background image by using the signatureBackgroundImage parameter.
This involves sending the image as a buffer for the customization:
Copy
const backgroundImage = fs.readFileSync('backgroundImage.png');
formData.append('signatureBackgroundImage', backgroundImage, {
    filename: 'logo.png',
    contentType: 'image/png',
});

Step 2: Image Near The Signature:

If you don't have a paid account, you can still display your logo by using the signatureImage parameter. This allows you to place an image near the signature.
Copy
const signatureImage = fs.readFileSync('signatureImage.png')
formData.append('signatureImage', signatureImage, {
    filename: 'logo.png',
    contentType: 'image/png',
});

Step 3: Signature parameters

In the documentation, you can find various parameters to customize the signature display and its values:
timezone: Timezone for the signature timestamp in PHP format. Default is UTC. View the list of all available timezones
signatureFieldName: Unique field name for the signature (default will set an automatically unique name)
signatureXPosition: Set the X position of the signature in pixels relative to the left edge of the PDF. A value of 0 corresponds to the far left. (Default is 0).
signatureYPosition: Set the Y position of the signature in pixels relative to the bottom of the PDF. The value of 0 corresponds to the very bottom of the PDF. (Default is 0)
signatureWidth: Width in pixels of the signature (default 100)
signatureHeight: Height in pixels of the signature (default 50)
signatureMessage: Visual message label for the signature (default 'Signed digitally')
signaturePageAppearance: Page(s) which will appear the signature. Can be an integer to specify a single page, an array of integers to specify multiple pages, or -1 to signify all pages. Default is 1.
signatureDateLabel: Label for the signature date (default 'Date: ')
signatureDateFormat: Format for the signature date in PHP format (default 'd-m-Y H:i:s')
signarureReasonLabel: Label for the signature reason (default 'Reason:')
signatureReason: Reason for the signature (default null)
signatureLocation: Location of the signature (default null)
signatureContactInfo: Contract info for the signature (default null)
signatureShowDistinguishedName: Show the Distinguished Name (DN) in the signature, including the proprietary name and all information related to the signature (default: false).
You can find some examples in the following code:
Copy
// Customize signature appearance and position
formData.append('signaturePageAppearance', -1); // Page number for signature (-1 for all pages or specify an array of pages)
formData.append('timezone', 'UTC'); // Timezone for signature date and time
formData.append('signatureMessage', 'Digitally signed by the user'); // Reason/message for the signature
formData.append('signatureDateLabel', ''); // Label for the signature date (empty string for no label)
formData.append('signatureDateFormat', 'Y-m-d H:i:s'); // Format for the signature date
formData.append('signatureHeight', 100); // Signature height in pixels
formData.append('signatureWidth', 150); // Signature width in pixels
formData.append('signatureYPosition', 100); // Y position of the signature relative to the bottom of the page
formData.append('signatureXPosition', 180); // X position of the signature relative to the left of the page

Step 4: PDF Metadata

When you sign a PDF, PDF Signify also allows you to modify the metadata. The following parameters are available for this purpose:
pdfMetadataTitle: Title of the PDF document.
pdfMetadataSubject: Subject of the PDF document.
pdfMetadataAuthor: Author of the PDF document.
pdfMetadataKeywords: Keywords associated with the PDF document.
pdfMetadataCreator: Creator application of the PDF document.
pdfMetadataProducer: Producer application of the PDF document.
pdfMetadataCreationDate: Creation date of the PDF document in ISO 8601 format (e.g.,2024–01–01 10:00:00).
pdfMetadataModificationDate: Last modification date of the PDF document in ISO 8601 format (e.g.,2024–01–01 10:00:00).
Copy
// Optional: Modify PDF metadata (e.g., author, title, keywords)
formData.append('pdfMetadataAuthor', 'AUTHOR'); // PDF author
formData.append('pdfMetadataKeywords', 'keywords,keyword2'); // PDF keywords
formData.append('pdfMetadataTitle', 'MY DOCUMENT'); // PDF title
formData.append('pdfMetadataSubject', 'EXAMPLE DOCUMENT'); // PDF subject
formData.append('pdfMetadataCreator', 'PDFSIGNIFY'); // PDF creator software
formData.append('pdfMetadataProducer', 'PDFSIGNIFY'); // PDF producer software
formData.append('pdfMetadataCreationDate', new Date().toLocaleString()); // Creation date
formData.append('pdfMetadataModificationDate', new Date().toLocaleString()); // Modification date
IMPORTANT: You can also use the /api/v1/set-pdf-metadata endpoint with the same parameters to change the PDF metadata without needing to sign the document. You can find more information here.
You can find the complete file server.jswith all the examples:
Copy
const express = require('express');
const fs = require('fs'); // Import fs module for file system operations
const axios = require('axios'); // Import Axios for making HTTP requests
const FormData = require('form-data'); // Import FormData for handling multipart form data

const app = express();
const port = 3000;

app.use(express.json());

// Basic route to test the server
app.get('/', (req, res) => {
  res.send('PDF Signing Server is up and running!');
});

app.get('/sign-pdf', async (req, res) => {
    // Read the digital certificate file (can be .pfx or .p12 format)
    const cert = fs.readFileSync('certificate.pfx');

    // Read the PDF document to be signed
    const pdf = fs.readFileSync('filepdf.pdf');

    // Import FormData for constructing the form data (not needed in client-side JavaScript)
    const formData = new FormData();

    // Append the digital certificate to the form data
    formData.append('certificate', cert, {
        filename: 'certificate.pfx',
        contentType: 'application/x-pkcs12',
    });

    // Append the password for the digital certificate
    formData.append('certificatePassword', 'password');

    // Append the PDF document to the form data
    formData.append('pdf', pdf, {
        filename: 'filepdf.pdf',
        contentType: 'application/pdf',
    });

    // Append the custom background image to the form data (optional)
    const backgroundImage = fs.readFileSync('signatureBackgroundImage.png');
    formData.append('signatureBackgroundImage', backgroundImage, {
        filename: 'logo.png',
        contentType: 'image/png',
    });

    // // Append the custom  image near the signature (optional)
    // const signatureImage = fs.readFileSync('signatureImage.png')
    // formData.append('signatureImage', signatureImage, {
    //     filename: 'logo.png',
    //     contentType: 'image/png',
    // });

    // Customize signature appearance and position
    formData.append('signaturePageAppearance', -1); // Page number for signature (-1 for all pages or specify an array of pages)
    formData.append('timezone', 'UTC'); // Timezone for signature date and time
    formData.append('signatureMessage', 'Digitally signed by the user'); // Reason/message for the signature
    formData.append('signatureDateLabel', ''); // Label for the signature date (empty string for no label)
    formData.append('signatureDateFormat', 'Y-m-d H:i:s'); // Format for the signature date
    formData.append('signatureHeight', 100); // Signature height in pixels
    formData.append('signatureWidth', 150); // Signature width in pixels
    formData.append('signatureYPosition', 100); // Y position of the signature relative to the bottom of the page
    formData.append('signatureXPosition', 180); // X position of the signature relative to the left of the page

    // Optional: Modify PDF metadata (e.g., author, title, keywords)
    formData.append('pdfMetadataAuthor', 'AUTHOR'); // PDF author
    formData.append('pdfMetadataKeywords', 'keywords,keyword2'); // PDF keywords
    formData.append('pdfMetadataTitle', 'MY DOCUMENT'); // PDF title
    formData.append('pdfMetadataSubject', 'EXAMPLE DOCUMENT'); // PDF subject
    formData.append('pdfMetadataCreator', 'PDFSIGNIFY'); // PDF creator software
    formData.append('pdfMetadataProducer', 'PDFSIGNIFY'); // PDF producer software
    formData.append('pdfMetadataCreationDate', new Date().toLocaleString()); // Creation date
    formData.append('pdfMetadataModificationDate', new Date().toLocaleString()); // Modification date

    // Send the request to sign the PDF document
    try {
        // Make a POST request using axios with form data
        const response = await axios.post('https://api.pdfsignify.com/api/v1/sign-pdf', formData, {
            headers: {
                contentType: 'multipart/form-data', // Important: set content type to 'multipart/form-data'
                'AccessKey': 'MY_ACCESS_KEY', // Your access key for the API
                'SecretKey': 'MY_SECRET_KEY' // Your secret key for the API
            },
            responseType: 'arraybuffer' // The response will be in arrayBuffer format to handle the returned PDF
        });

        // Send the signed PDF as a response
        res.contentType('application/pdf');
        return res.send(response.data);
    } catch (error) {
        let errorResponse = error
        if(error?.response?.data){
            errorResponse = error.response.data.toString()
        }
        // Log any errors and return the error object
        console.error('error',errorResponse);
        res.send(errorResponse);
    }
});

// Example route for generating and signing a PDF (to be implemented)
app.post('/sign-pdf', async (req, res) => {
  // PDF generation and signing logic will go here
  res.send('PDF signed successfully!');
});

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

Step 5: Verify Certificate Validity and Password Match

PDF Signify also allows you to verify if a certificate is valid and if the password matches.
To perform this check, use the /api/v1/check-certificate-password endpoint. You can find more information here.
This method uses only two parameters: the certificate (certificate), as used previously, and the certificate password (certificatePassword).
You can use the following example with the (/check-certificate)endpoint:
Copy
app.get('/check-certificate', async (req, res) => {
    // Read the digital certificate file (can be .pfx or .p12 format)
    const cert = fs.readFileSync('certificate.pfx');

    // Import FormData for constructing the form data (not needed in client-side JavaScript)
    const formData = new FormData();

    // Append the digital certificate to the form data
    formData.append('certificate', cert, {
        filename: 'certificate.pfx',
        contentType: 'application/x-pkcs12',
    });

    // Append the password for the digital certificate
    formData.append('certificatePassword', 'password');

    // Send the request to sign the PDF document
    try {
        // Make a POST request using axios with form data
        const response = await axios.post('https://api.pdfsignify.com/api/v1/check-certificate-password', formData, {
            headers: {
                contentType: 'multipart/form-data', // Important: set content type to 'multipart/form-data'
                'AccessKey': 'MY_ACCESS_KEY', // Your access key for the API
                'SecretKey': 'MY_SECRET_KEY' // Your secret key for the API
            },
        });

        return res.send(response.data);
    } catch (error) {
        let errorResponse = error
        if(error?.response?.data){
            errorResponse = error.response.data
        }
        // Log any errors and return the error object
        console.error('error',errorResponse);
        res.send(errorResponse);
    }
});
In this case, the response will be in application/json format and may indicate success as follows:
Copy
{'success':true,'message':'The certificate password is correct!','errors':[],'data':{},'auth':true}
Alternatively, the response may indicate that the certificate or password is not valid, as shown below:
Copy
{'success':false,'message':'The certificate password is not correct!','errors':['certificatePassword'],'data':{},'auth':true}

Part 7: Conclusions

In conclusion, we have explored a comprehensive example of how to sign PDFs using Node.js and the PDF Signify application. We've seen how this service can assist in signing any PDF document, setting PDF metadata, or verifying a certificate with ease, and it can be integrated with various technologies.
For more information, you can visit the following resources:
If you have further questions, you can contact PDF Signify support via email.