Node.js is an excellent environment for integrating PDF signing into your backend. With its non-blocking I/O and rich ecosystem of HTTP libraries, you can build a signing service that handles high throughput without complexity. This guide covers a complete integration with PDFSignify's API, including all three endpoints and production-ready patterns.
How PDFSignify Works with Node.js
PDFSignify is a synchronous, stateless API. You send a PDF and a digital certificate via multipart/form-data, and you receive the signed PDF as binary data in the response. There are no webhooks to configure, no document IDs to track, and no polling for status. This makes it particularly simple to integrate in Node.js — every signing operation is a single HTTP request.
Authentication
Every request requires two HTTP headers: AccessKey and SecretKey. You'll get these from your PDFSignify dashboard. Store them in environment variables — never hardcode credentials.
# .env file
PDFSIGNIFY_ACCESS_KEY=your_access_key_here
PDFSIGNIFY_SECRET_KEY=your_secret_key_here
CERT_PASSWORD=your_certificate_passwordSetup: Installing Dependencies
npm install axios form-data dotenvBuilding a Reusable PDFSignify Client
Rather than repeating the authentication headers and base URL everywhere, create a small helper module that wraps the three API endpoints.
import axios from "axios";
import FormData from "form-data";
import * as fs from "fs";
import "dotenv/config";
const BASE_URL = "https://api.pdfsignify.com/api/v1";
const authHeaders = {
"AccessKey": process.env.PDFSIGNIFY_ACCESS_KEY,
"SecretKey": process.env.PDFSIGNIFY_SECRET_KEY
};
export async function signPdf(pdfPath, certPath, certPassword, options = {}) {
const formData = new FormData();
formData.append("certificate", fs.readFileSync(certPath), {
filename: "certificate.pfx",
contentType: "application/x-pkcs12"
});
formData.append("certificatePassword", certPassword);
formData.append("pdf", fs.readFileSync(pdfPath), {
filename: "document.pdf",
contentType: "application/pdf"
});
// Apply optional fields
for (const [key, value] of Object.entries(options)) {
if (key === "signatureBackgroundImage") {
formData.append(key, fs.readFileSync(value), {
filename: "background.png",
contentType: "image/png"
});
} else {
formData.append(key, String(value));
}
}
const response = await axios.post(BASE_URL + "/sign-pdf", formData, {
headers: { ...formData.getHeaders(), ...authHeaders },
responseType: "arraybuffer"
});
return Buffer.from(response.data);
}
export async function setPdfMetadata(pdfPath, metadata) {
const formData = new FormData();
formData.append("pdf", fs.readFileSync(pdfPath), {
filename: "document.pdf",
contentType: "application/pdf"
});
for (const [key, value] of Object.entries(metadata)) {
formData.append(key, value);
}
const response = await axios.post(BASE_URL + "/set-pdf-metadata", formData, {
headers: { ...formData.getHeaders(), ...authHeaders },
responseType: "arraybuffer"
});
return Buffer.from(response.data);
}
export async function checkCertificatePassword(certPath, certPassword) {
const formData = new FormData();
formData.append("certificate", fs.readFileSync(certPath), {
filename: "certificate.pfx",
contentType: "application/x-pkcs12"
});
formData.append("certificatePassword", certPassword);
const response = await axios.post(
BASE_URL + "/check-certificate-password",
formData,
{ headers: { ...formData.getHeaders(), ...authHeaders } }
);
return response.data;
}Endpoint 1: Sign a PDF
The sign-pdf endpoint is the core of the API. It takes a PDF, a .pfx/.p12 digital certificate, and the certificate password. It returns the signed PDF as binary data. Here's how to use the client we built above:
import { signPdf } from "./pdfsignify-client.js";
import * as fs from "fs";
const signedPdf = await signPdf(
"contracts/agreement.pdf",
"certs/company.pfx",
process.env.CERT_PASSWORD,
{
signatureMessage: "Signed by Automated System",
signaturePageAppearance: "-1",
signatureHeight: "80",
signatureWidth: "200",
signatureXPosition: "350",
signatureYPosition: "50",
timezone: "UTC",
signatureDateFormat: "Y-m-d H:i:s",
pdfMetadataAuthor: "ACME Corporation",
pdfMetadataTitle: "Service Agreement"
}
);
fs.writeFileSync("contracts/agreement-signed.pdf", signedPdf);
console.log("Contract signed successfully!");Endpoint 2: Set PDF Metadata
Use this endpoint to update metadata fields on a PDF without signing it. This is useful for document preparation pipelines where you need to stamp author, title, or keyword information before archiving or distributing.
import { setPdfMetadata } from "./pdfsignify-client.js";
import * as fs from "fs";
const updatedPdf = await setPdfMetadata("report.pdf", {
pdfMetadataAuthor: "Finance Department",
pdfMetadataTitle: "Q1 2026 Revenue Report",
pdfMetadataSubject: "Quarterly Financial Report",
pdfMetadataKeywords: "finance,revenue,Q1,2026",
pdfMetadataCreator: "ACME Reporting System",
pdfMetadataProducer: "PDFSignify"
});
fs.writeFileSync("report-with-metadata.pdf", updatedPdf);
console.log("Metadata updated!");Endpoint 3: Check Certificate Password
Before processing a batch of documents, validate that the certificate and password are correct. This avoids wasting API calls on a bad certificate.
import { checkCertificatePassword } from "./pdfsignify-client.js";
const result = await checkCertificatePassword(
"certs/company.pfx",
process.env.CERT_PASSWORD
);
if (result.success) {
console.log("Certificate is valid — ready to sign!");
} else {
console.error("Invalid certificate or password.");
}Express API: Signing PDFs on Demand
A common pattern is to build an internal API that your frontend or other services call to sign documents. Here's a production-ready Express endpoint with error handling.
import express from "express";
import multer from "multer";
import { signPdf } from "./pdfsignify-client.js";
import * as fs from "fs";
const app = express();
const upload = multer({ dest: "tmp/" });
app.post("/api/sign-document", upload.single("pdf"), async (req, res) => {
if (!req.file) {
return res.status(400).json({ error: "No PDF file provided" });
}
try {
const signedPdf = await signPdf(
req.file.path,
"certs/company.pfx",
process.env.CERT_PASSWORD,
{
signatureMessage: req.body.message || "Digitally signed",
signaturePageAppearance: "-1"
}
);
// Clean up temp file
fs.unlinkSync(req.file.path);
res.contentType("application/pdf");
res.send(signedPdf);
} catch (error) {
fs.unlinkSync(req.file.path);
console.error("Signing error:", error.message);
res.status(500).json({ error: "Failed to sign document" });
}
});
app.listen(3000, () => console.log("Signing service running on :3000"));Batch Signing: Processing Multiple PDFs
Since PDFSignify is synchronous, batch signing is straightforward. Process documents sequentially or use Promise.allSettled for controlled parallelism.
import { signPdf } from "./pdfsignify-client.js";
import * as fs from "fs";
import * as path from "path";
async function signBatch(pdfDir, outputDir, certPath, certPassword) {
const files = fs.readdirSync(pdfDir).filter(f => f.endsWith(".pdf"));
console.log(`Signing ${files.length} documents...`);
const results = await Promise.allSettled(
files.map(async (file) => {
const signed = await signPdf(
path.join(pdfDir, file),
certPath,
certPassword,
{ signatureMessage: "Batch signed" }
);
fs.writeFileSync(path.join(outputDir, `signed-${file}`), signed);
return file;
})
);
const succeeded = results.filter(r => r.status === "fulfilled").length;
const failed = results.filter(r => r.status === "rejected").length;
console.log(`Done: ${succeeded} signed, ${failed} failed.`);
}
signBatch("invoices/", "invoices/signed/", "certs/company.pfx", process.env.CERT_PASSWORD);Production Best Practices
- Store API credentials and certificate passwords in environment variables or a secrets manager
- Keep your .pfx/.p12 certificate files in a secure location with restricted file permissions
- Validate certificates with /check-certificate-password before starting batch jobs
- Use try/catch around every signing call — handle network errors and API errors separately
- Clean up temporary files after signing to avoid filling disk
- Monitor response times — signing is synchronous, so factor it into your timeout settings
- For high-throughput use cases, limit concurrency to avoid overwhelming the API
Summary
Integrating PDFSignify in Node.js is straightforward because the API itself is simple: send files, get a signed PDF back. There's no state management, no async workflows, and no polling. Build a small client wrapper, use it from your Express routes or batch scripts, and you'll have a production-ready signing pipeline in less than an hour.
PDFSignify's synchronous design means your Node.js integration is just a function call away — no queues, no webhooks, no complexity.