DEV Community

Hichem Bed
Hichem Bed

Posted on

Auto-Generate PDF Invoices in Your SaaS App with One API Call

Every SaaS app eventually needs to send invoices. And every time, developers reach for a PDF library — pdfkit, puppeteer, jsPDF — and spend days wiring it up.

There's a better way. Here's how to auto-generate a pixel-perfect PDF invoice from a Stripe webhook in under 20 lines of code.

The Problem with PDF Libraries

If you've ever used pdfkit or puppeteer directly in production, you know the pain:

  • You bundle a headless browser or a heavy library into your app
  • You write 200+ lines of layout code for a simple invoice
  • It breaks on every Node.js version upgrade
  • You deal with font rendering, page breaks, and margins manually

For a SaaS app, this is a distraction from your core product.

The Setup: Stripe Webhook + RenderPDFs

Here's the full flow:

  1. Customer pays → Stripe fires invoice.payment_succeeded
  2. Your webhook handler builds an HTML invoice template
  3. You call RenderPDFs API → get back a PDF URL
  4. You email the PDF link to your customer

Let's build it.

Step 1: The Invoice HTML Template

Design your invoice in plain HTML and CSS — no special PDF syntax needed:

function buildInvoiceHTML(invoice) {
  return `
    <html>
    <head>
      <style>
        body { font-family: Arial, sans-serif; padding: 40px; color: #333; }
        .header { display: flex; justify-content: space-between; margin-bottom: 40px; }
        .company { font-size: 24px; font-weight: bold; color: #6d28d9; }
        table { width: 100%; border-collapse: collapse; margin-top: 20px; }
        th { background: #f3f4f6; padding: 12px; text-align: left; }
        td { padding: 12px; border-bottom: 1px solid #e5e7eb; }
        .total { font-size: 18px; font-weight: bold; text-align: right; margin-top: 20px; }
      </style>
    </head>
    <body>
      <div class="header">
        <div class="company">Acme SaaS</div>
        <div>Invoice #${invoice.number}<br>${new Date(invoice.created * 1000).toLocaleDateString()}</div>
      </div>
      <p>Bill to: <strong>${invoice.customer_email}</strong></p>
      <table>
        <tr><th>Description</th><th>Amount</th></tr>
        ${invoice.lines.data.map(line => `
          <tr><td>${line.description}</td><td>$${(line.amount / 100).toFixed(2)}</td></tr>
        `).join("")}
      </table>
      <div class="total">Total: $${(invoice.amount_paid / 100).toFixed(2)}</div>
    </body>
    </html>
  `;
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Call RenderPDFs from Your Stripe Webhook

import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

export async function POST(req) {
  const sig = req.headers["stripe-signature"];
  const event = stripe.webhooks.constructEvent(
    await req.text(),
    sig,
    process.env.STRIPE_WEBHOOK_SECRET
  );

  if (event.type === "invoice.payment_succeeded") {
    const invoice = event.data.object;

    // Build HTML invoice
    const html = buildInvoiceHTML(invoice);

    // Generate PDF with RenderPDFs
    const response = await fetch("https://api.renderpdfs.com/v1/pdf/html", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.RENDERPDFS_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        html,
        options: { format: "A4", printBackground: true },
      }),
    });

    const { url } = await response.json();

    // Email the PDF link to your customer
    await sendEmail({
      to: invoice.customer_email,
      subject: `Your invoice #${invoice.number}`,
      body: `Your invoice is ready: ${url}`,
    });
  }

  return new Response("ok");
}
Enter fullscreen mode Exit fullscreen mode

That's it. No library to maintain. No Puppeteer in your bundle. No PDF layout headaches.

Why This Works Better

Approach Bundle size Setup time Maintenance
pdfkit +15MB 2–3 days High
Puppeteer in-app +300MB 3–5 days Very high
RenderPDFs API 0MB 30 min None

The PDF URL returned has a 24-hour expiry — perfect for emailing customers without storing files yourself.

Get Started

  1. Sign up at renderpdfs.com — free tier included
  2. Grab your API key from the dashboard
  3. Add RENDERPDFS_API_KEY to your environment variables
  4. Copy the webhook code above and adapt your HTML template

First PDF in under 60 seconds. No credit card required.


Have questions about the implementation? Drop them in the comments — happy to help! 🚀

Top comments (0)