This tutorial walks you through building a complete PDF signing integration using the PDFSignify API. By the end, you'll have working code that signs PDFs with a digital certificate, customizes the signature appearance, and sets document metadata — all in a single API call.
We'll use Node.js for the examples, but the API is a standard REST service that works with any language or HTTP client.
Prerequisites
- A PDFSignify account — sign up at pdfsignify.com to get your AccessKey and SecretKey
- A .pfx or .p12 digital certificate (you can use a self-signed certificate for testing)
- Node.js 18+ installed (or any language with an HTTP client)
- A PDF file to sign
Step 1: Set Up Your Project
Create a new project directory and install the two dependencies we need: axios for HTTP requests and form-data for building multipart requests.
mkdir pdf-signing-demo && cd pdf-signing-demo
npm init -y
npm install axios form-dataPlace your certificate file (certificate.pfx) and a test PDF (document.pdf) in the project directory. Then create your main script file:
// sign.mjs
import axios from "axios";
import * as fs from "fs";
import FormData from "form-data";
const ACCESS_KEY = process.env.PDFSIGNIFY_ACCESS_KEY;
const SECRET_KEY = process.env.PDFSIGNIFY_SECRET_KEY;
const CERT_PASSWORD = process.env.CERT_PASSWORD;
if (!ACCESS_KEY || !SECRET_KEY) {
console.error("Missing API credentials. Set PDFSIGNIFY_ACCESS_KEY and PDFSIGNIFY_SECRET_KEY.");
process.exit(1);
}Step 2: Validate Your Certificate
Before signing anything, let's verify that the certificate and password are valid. This is especially useful when users upload their own certificates — you can catch errors early instead of failing mid-batch.
async function validateCertificate(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(
"https://api.pdfsignify.com/api/v1/check-certificate-password",
formData,
{
headers: {
...formData.getHeaders(),
"AccessKey": ACCESS_KEY,
"SecretKey": SECRET_KEY
}
}
);
return response.data.success;
}
const isValid = await validateCertificate("certificate.pfx", CERT_PASSWORD);
if (!isValid) {
console.error("Invalid certificate or password.");
process.exit(1);
}
console.log("Certificate validated successfully.");Step 3: Sign Your First PDF
Now let's sign a PDF. The sign-pdf endpoint accepts multipart/form-data with three required fields: the certificate file, the certificate password, and the PDF to sign. The response body is the signed PDF as binary data.
async function signPdf(pdfPath, certPath, certPassword) {
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"
});
const response = await axios.post(
"https://api.pdfsignify.com/api/v1/sign-pdf",
formData,
{
headers: {
...formData.getHeaders(),
"AccessKey": ACCESS_KEY,
"SecretKey": SECRET_KEY
},
responseType: "arraybuffer"
}
);
return Buffer.from(response.data);
}
const signedPdf = await signPdf("document.pdf", "certificate.pfx", CERT_PASSWORD);
fs.writeFileSync("signed-document.pdf", signedPdf);
console.log("PDF signed and saved to signed-document.pdf");Step 4: Customize the Signature Appearance
By default, PDFSignify applies a minimal visible signature. You can customize every aspect of it: the position, size, message text, date format, timezone, and even a background image (like a company logo or a handwritten signature scan).
async function signPdfWithCustomSignature(pdfPath, certPath, certPassword) {
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"
});
// Signature customization
formData.append("signatureMessage", "Digitally signed by ACME Corp");
formData.append("signatureDateLabel", "Date:");
formData.append("signatureDateFormat", "Y-m-d H:i:s");
formData.append("timezone", "Europe/London");
formData.append("signaturePageAppearance", "-1"); // Show on all pages
formData.append("signatureXPosition", "350");
formData.append("signatureYPosition", "50");
formData.append("signatureWidth", "200");
formData.append("signatureHeight", "80");
// Optional: add a background image
if (fs.existsSync("company-logo.png")) {
formData.append("signatureBackgroundImage", fs.readFileSync("company-logo.png"), {
filename: "company-logo.png",
contentType: "image/png"
});
}
const response = await axios.post(
"https://api.pdfsignify.com/api/v1/sign-pdf",
formData,
{
headers: {
...formData.getHeaders(),
"AccessKey": ACCESS_KEY,
"SecretKey": SECRET_KEY
},
responseType: "arraybuffer"
}
);
return Buffer.from(response.data);
}Step 5: Set PDF Metadata
You can also update a PDF's metadata without signing it. This is useful for tagging documents before archiving, or for adding organizational information to generated PDFs.
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(
"https://api.pdfsignify.com/api/v1/set-pdf-metadata",
formData,
{
headers: {
...formData.getHeaders(),
"AccessKey": ACCESS_KEY,
"SecretKey": SECRET_KEY
},
responseType: "arraybuffer"
}
);
return Buffer.from(response.data);
}
const taggedPdf = await setPdfMetadata("document.pdf", {
pdfMetadataAuthor: "ACME Corporation",
pdfMetadataTitle: "Service Agreement",
pdfMetadataSubject: "Legal",
pdfMetadataKeywords: "contract,agreement,2026"
});
fs.writeFileSync("tagged-document.pdf", taggedPdf);Step 6: Build a Reusable Signing Service
In a production application, you'll want to wrap the API calls in a reusable service class. Here's a pattern that works well for Express or Fastify backends:
class PdfSigningService {
constructor(accessKey, secretKey) {
this.accessKey = accessKey;
this.secretKey = secretKey;
this.baseUrl = "https://api.pdfsignify.com/api/v1";
}
async sign(pdfBuffer, certBuffer, certPassword, options = {}) {
const formData = new FormData();
formData.append("certificate", certBuffer, {
filename: "certificate.pfx",
contentType: "application/x-pkcs12"
});
formData.append("certificatePassword", certPassword);
formData.append("pdf", pdfBuffer, {
filename: "document.pdf",
contentType: "application/pdf"
});
for (const [key, value] of Object.entries(options)) {
formData.append(key, value);
}
const response = await axios.post(
this.baseUrl + "/sign-pdf",
formData,
{
headers: {
...formData.getHeaders(),
"AccessKey": this.accessKey,
"SecretKey": this.secretKey
},
responseType: "arraybuffer"
}
);
return Buffer.from(response.data);
}
}
// Usage
const signer = new PdfSigningService(ACCESS_KEY, SECRET_KEY);
const signed = await signer.sign(pdfBuffer, certBuffer, "password", {
signatureMessage: "Approved",
signaturePageAppearance: "-1"
});Step 7: Handle Errors Gracefully
The PDFSignify API returns standard HTTP status codes. Wrap your calls in try/catch blocks and handle common failure scenarios:
try {
const signedPdf = await signPdf("document.pdf", "certificate.pfx", CERT_PASSWORD);
fs.writeFileSync("signed.pdf", signedPdf);
} catch (error) {
if (error.response) {
const status = error.response.status;
const body = error.response.data.toString();
if (status === 401) {
console.error("Authentication failed. Check your AccessKey and SecretKey.");
} else if (status === 400) {
console.error("Bad request:", body);
} else {
console.error("API error (" + status + "):", body);
}
} else {
console.error("Network error:", error.message);
}
}Running the Complete Script
Set your environment variables and run the script:
export PDFSIGNIFY_ACCESS_KEY="your_access_key"
export PDFSIGNIFY_SECRET_KEY="your_secret_key"
export CERT_PASSWORD="your_cert_password"
node sign.mjsYou should see "PDF signed and saved to signed-document.pdf" in your terminal. Open the signed PDF in any PDF reader — you'll see the digital signature in the signature panel, confirming the document's integrity and the signer's identity.
Integrating Into a Web Application
In a real application, the flow typically looks like this: your backend receives a PDF (from a file upload, a document generator, or a database), reads the certificate from a secure store, calls the PDFSignify API, and then stores or serves the signed PDF. Since the API is synchronous, your endpoint can return the signed document directly to the user.
// Express route example
app.post("/api/sign", upload.single("pdf"), async (req, res) => {
const certBuffer = await getSecretFromVault("signing-certificate");
const certPassword = await getSecretFromVault("cert-password");
const formData = new FormData();
formData.append("certificate", certBuffer, {
filename: "certificate.pfx",
contentType: "application/x-pkcs12"
});
formData.append("certificatePassword", certPassword);
formData.append("pdf", req.file.buffer, {
filename: req.file.originalname,
contentType: "application/pdf"
});
const response = await axios.post(
"https://api.pdfsignify.com/api/v1/sign-pdf",
formData,
{
headers: {
...formData.getHeaders(),
"AccessKey": process.env.PDFSIGNIFY_ACCESS_KEY,
"SecretKey": process.env.PDFSIGNIFY_SECRET_KEY
},
responseType: "arraybuffer"
}
);
res.set("Content-Type", "application/pdf");
res.set("Content-Disposition", "attachment; filename=signed-" + req.file.originalname);
res.send(Buffer.from(response.data));
});What's Next?
- Batch signing — loop through multiple PDFs and sign each one sequentially or in parallel
- Certificate management — store certificates in AWS Secrets Manager, Azure Key Vault, or HashiCorp Vault
- Signature templates — pre-configure signature appearance options for different document types
- Audit logging — record every signing operation with timestamps and document hashes for compliance
The PDFSignify API is stateless and synchronous, which makes it easy to integrate into any architecture — from serverless functions to monolithic backends. Start simple, sign one PDF, and build from there.
Start simple, then scale. One API call to sign a PDF — that's all it takes to get started.