DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

Automated Invoice PDFs on Stripe Payment Success (5-Minute Setup)

Automated Invoice PDFs on Stripe Payment Success (5-Minute Setup)

Every SaaS needs to send invoices to customers. Most teams build rickety PDF generation pipelines: they use Puppeteer on a background worker, manage browser processes, deal with memory leaks, and wonder why their invoice job crashed at 2 AM.

Here's a simpler approach: generate invoice PDFs on Stripe charge.succeeded webhooks using PageBolt's API. No browser processes. No infrastructure overhead. Just Stripe → API call → PDF email.

The Setup: 5 Lines of Webhook Handler

First, install dependencies:

npm install express stripe dotenv node-fetch
Enter fullscreen mode Exit fullscreen mode

Create your webhook handler:

const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const fetch = require('node-fetch');
const app = express();

app.use(express.json());

// Stripe webhook endpoint
app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];

  let event;
  try {
    event = stripe.webhooks.constructEvent(
      req.body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  if (event.type === 'charge.succeeded') {
    const charge = event.data.object;
    await generateAndEmailInvoice(charge);
  }

  res.json({received: true});
});

app.listen(3000, () => console.log('Webhook listening on port 3000'));
Enter fullscreen mode Exit fullscreen mode

Generate the Invoice PDF

Here's the function that generates the PDF and emails it:

async function generateAndEmailInvoice(charge) {
  const customerId = charge.customer;
  const customer = await stripe.customers.retrieve(customerId);

  // Generate HTML invoice
  const invoiceHtml = `
    <html>
      <head>
        <style>
          body { font-family: Arial; margin: 40px; color: #333; }
          .header { text-align: center; margin-bottom: 40px; }
          .logo { font-size: 28px; font-weight: bold; }
          .invoice-details { margin-bottom: 30px; }
          .line { display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid #eee; }
          .total { font-weight: bold; font-size: 18px; padding-top: 20px; border-top: 2px solid #000; }
          .footer { margin-top: 40px; font-size: 12px; color: #666; }
        </style>
      </head>
      <body>
        <div class="header">
          <div class="logo">Your Company</div>
          <h2>Invoice</h2>
        </div>

        <div class="invoice-details">
          <p><strong>Bill To:</strong><br>
            ${customer.name}<br>
            ${customer.email}
          </p>
          <p><strong>Invoice Date:</strong> ${new Date(charge.created * 1000).toLocaleDateString()}</p>
          <p><strong>Invoice ID:</strong> ${charge.id.substring(0, 8).toUpperCase()}</p>
        </div>

        <table style="width: 100%; border-collapse: collapse;">
          <tr style="background: #f5f5f5;">
            <th style="text-align: left; padding: 10px;">Description</th>
            <th style="text-align: right; padding: 10px;">Amount</th>
          </tr>
          <tr>
            <td style="padding: 10px;">Service Charge</td>
            <td style="text-align: right; padding: 10px;">$${(charge.amount / 100).toFixed(2)}</td>
          </tr>
        </table>

        <div class="total" style="text-align: right;">
          Total: $${(charge.amount / 100).toFixed(2)}
        </div>

        <div class="footer">
          <p>Thank you for your payment.</p>
          <p>Questions? Contact billing@yourcompany.com</p>
        </div>
      </body>
    </html>
  `;

  // Generate PDF with PageBolt
  const pdfResponse = await fetch('https://api.pagebolt.com/v1/pdf', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.PAGEBOLT_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      html: invoiceHtml,
      format: 'A4',
      margin: {top: 10, right: 10, bottom: 10, left: 10}
    })
  });

  if (!pdfResponse.ok) {
    console.error('PDF generation failed:', await pdfResponse.text());
    return;
  }

  const pdfBuffer = await pdfResponse.buffer();

  // Email the PDF (example using Sendgrid)
  const sgMail = require('@sendgrid/mail');
  sgMail.setApiKey(process.env.SENDGRID_API_KEY);

  await sgMail.send({
    to: customer.email,
    from: 'billing@yourcompany.com',
    subject: `Invoice ${charge.id.substring(0, 8).toUpperCase()}`,
    text: 'Please find your invoice attached.',
    attachments: [{
      content: pdfBuffer.toString('base64'),
      filename: `invoice-${charge.id.substring(0, 8)}.pdf`,
      type: 'application/pdf',
      disposition: 'attachment'
    }]
  });

  console.log(`Invoice emailed to ${customer.email}`);
}
Enter fullscreen mode Exit fullscreen mode

Environment Setup

Create a .env file:

STRIPE_SECRET_KEY=sk_live_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
PAGEBOLT_API_KEY=pf_live_xxx
SENDGRID_API_KEY=SG.xxx
Enter fullscreen mode Exit fullscreen mode

Get your Stripe webhook secret:

  1. Go to Stripe Dashboard → Developers → Webhooks
  2. Create webhook endpoint pointing to https://yourserver.com/webhook
  3. Subscribe to charge.succeeded event
  4. Copy the signing secret

Deploy to Production

Using Vercel (serverless):

# Install vercel CLI
npm install -g vercel

# Deploy
vercel --prod
Enter fullscreen mode Exit fullscreen mode

Update your Stripe webhook endpoint URL to your Vercel function URL.

Why This Beats Puppeteer

Puppeteer approach:

  • Spin up browser process for each invoice (~500ms overhead)
  • Manage memory and handle OOM errors
  • Deploy to EC2 or Lambda with extra reserved capacity
  • Cost: $100-300/month for modest volume

PageBolt approach:

  • 3-line API call (~100ms)
  • Zero infrastructure overhead
  • Automatic scaling
  • Cost: $29/month Starter plan (handles 5,000 PDFs)

For 1,000 invoices/month, PageBolt saves you $100+/month in infrastructure while being faster and more reliable.

Next Steps

  1. Sign up free at pagebolt.dev/pricing — 100 PDFs/month
  2. Test locally: Generate a sample invoice PDF with your HTML template
  3. Deploy webhook to production
  4. Watch invoices auto-generate on every Stripe payment

That's it. No browser management. No background worker headaches. Just invoices, automatically.

Start free today — pagebolt.dev/pricing. 100 PDFs/month, no credit card required.

Top comments (0)