Generating PDFs with Node.js
This guide will show you how to integrate docpenny into your Node.js application. We’ll walk through a complete workflow: submitting a generation job, polling for its completion, and downloading the final document.
What do I need before starting?
Before you begin, ensure you have:
- Node.js 18+ installed.
- An API Key (get one from Dashboard Settings).
- A Template ID (upload one in the Templates dashboard).
1. Project Setup
Create a new directory for your project and initialize it:
mkdir pdf-gen-app
cd pdf-gen-app
npm init -y We’ll use the native fetch API available in modern Node.js environments.
2. The Implementation
Create a file named generate.js and add the following code. This script demonstrates the full lifecycle of a PDF generation job.
const API_KEY = 'your_api_key_here';
const TEMPLATE_ID = 'your_template_id_here';
const API_BASE_URL = 'https://api.docpenny.com/api';
async function generatePDF() {
// 1. Submit the Job
console.log('Submitting PDF generation job...');
const submitResponse = await fetch(`${API_BASE_URL}/jobs`, {
method: 'POST',
headers: {
'x-api-key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateId: TEMPLATE_ID,
data: JSON.stringify({
name: 'Satoshi Nakamoto',
date: new Date().toLocaleDateString(),
items: [
{ desc: 'Technical Whitepaper', qty: 1, price: 0 },
{ desc: 'Genesis Block', qty: 1, price: 50 }
]
})
}),
});
if (!submitResponse.ok) {
const error = await submitResponse.json();
console.error('Failed to submit job:', error);
return;
}
const { jobId } = await submitResponse.json();
console.log(`Job submitted successfully. Job ID: ${jobId}`);
// 2. Poll for Status
let status = 'pending';
const terminalStates = ['ready_full_zip', 'ready_partial_zip', 'all_tasks_downloaded', 'failed', 'expired'];
while (!terminalStates.includes(status)) {
console.log(`Checking status... (${status})`);
await new Promise(resolve => setTimeout(resolve, 2000));
const statusResponse = await fetch(`${API_BASE_URL}/jobs/${jobId}`, {
headers: { 'x-api-key': API_KEY }
});
const jobDetails = await statusResponse.json();
status = jobDetails.status;
}
if (status === 'failed' || status === 'expired') {
console.error(`Job ${status}.`);
return;
}
console.log('Job completed!');
// 3. Download the Result
// For bulk jobs, you can download a ZIP of all PDFs
console.log('Downloading ZIP archive...');
const downloadResponse = await fetch(`${API_BASE_URL}/download/zip/${jobId}`, {
headers: { 'x-api-key': API_KEY }
});
if (downloadResponse.ok) {
const fs = require('fs');
const buffer = await downloadResponse.arrayBuffer();
fs.writeFileSync(`job_${jobId}.zip`, Buffer.from(buffer));
console.log(`Saved result to job_${jobId}.zip`);
} else {
console.error('Failed to download result.');
}
}
generatePDF().catch(console.error); 3. Running the Script
Replace the placeholders with your actual credentials and run the script:
node generate.js What are the best practices?
Handling Webhooks
Polling is great for simplicity, but for high-volume applications, we recommend using Webhooks. You can provide a webhookUrl in the initial POST request, and we’ll notify your server as soon as the job is finished.
One-Time Download
The /api/download/zip/{jobId} endpoint only allows one download per job. After the first successful download, subsequent requests will return a 404. If you need to download the same content again, re-submit the generation job.
How do I verify webhook signatures?
To ensure that a webhook request actually came from docpenny, every request includes an X-Webhook-Signature header. You should verify this signature using your Webhook Secret found in your dashboard.
The signature is an HMAC-SHA256 hash of the raw request body, prefixed with sha256=.
Node.js (Express) Verification Example
const crypto = require('crypto');
function verifyWebhook(req, res, next) {
const signature = req.headers['x-webhook-signature'];
const secret = process.env.WEBHOOK_SECRET;
// Get raw body (requires express.raw() or similar)
const body = req.body.toString();
const hmac = crypto.createHmac('sha256', secret);
const digest = 'sha256=' + hmac.update(body).digest('hex');
if (signature === digest) {
next();
} else {
res.status(401).send('Invalid signature');
}
} Error Handling
Always check the status and error fields in the response. Common errors include insufficient_credits or validation_error (if your data doesn’t match the template requirements).
Secure your Keys
Never commit your API keys to version control. Use environment variables (e.g., via dotenv) to manage sensitive configuration.