PDFPipe

Use case

Webhook PDF API

Generate PDFs automatically when an event fires: a payment succeeds, a form is submitted, a contract is signed. One POST request from your webhook handler, a stored document and a URL back.

Stripe payment → invoice PDF

The most common pattern: listen for payment_intent.succeeded, render the invoice HTML, post to the API, store the URL in your database, and email it to the customer. The whole flow completes inside the webhook timeout.

Node.js / Express
// Express webhook handler: generate an invoice when payment succeeds
import express from "express";
import Stripe from "stripe";
import Mustache from "mustache";
import { readFileSync } from "fs";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const template = readFileSync("./templates/invoice.html", "utf-8");

app.post("/webhooks/stripe", express.raw({ type: "application/json" }), async (req, res) => {
  const sig = req.headers["stripe-signature"]!;
  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch {
    return res.status(400).send("Webhook signature verification failed.");
  }

  if (event.type === "payment_intent.succeeded") {
    const intent = event.data.object as Stripe.PaymentIntent;
    const order = await db.orders.findByPaymentIntent(intent.id);

    const html = Mustache.render(template, {
      invoiceNumber: order.invoiceNumber,
      customerName: order.customer.name,
      customerEmail: order.customer.email,
      lineItems: order.lineItems,
      subtotal: order.subtotal,
      tax: order.tax,
      total: order.total,
      issueDate: new Date().toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" }),
    });

    const pdfRes = await fetch("https://api.pdfpipe.xyz/v1/pdf", {
      method: "POST",
      headers: { Authorization: `Bearer ${process.env.PDFPIPE_KEY}`, "Content-Type": "application/json" },
      body: JSON.stringify({ html, store: true, options: { format: "A4" } }),
    });

    const { document_url, document_id } = await pdfRes.json();

    await db.invoices.create({
      orderId: order.id,
      pdfId: document_id,
      pdfUrl: document_url,
      stripePaymentId: intent.id,
    });

    await mailer.send({
      to: order.customer.email,
      subject: `Your invoice #${order.invoiceNumber}`,
      html: `<p>Thanks for your order! <a href="${document_url}">Download your invoice</a>.</p>`,
    });
  }

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

No-code: Zapier, Make, or n8n

The API is just an HTTP POST. Any automation tool that can make HTTP requests can trigger a PDF generation: Zapier, Make (Integromat), n8n, or a simple cURL in a shell script.

HTTP request config
// No-code: Zapier / Make / n8n
// Trigger: "Stripe payment succeeded" (or any form submission, CRM event, etc.)
// Action: HTTP POST to https://api.pdfpipe.xyz/v1/pdf
//
// Headers:
//   Authorization: Bearer pp_live_...
//   Content-Type: application/json
//
// Body (JSON):
// {
//   "html": "<!DOCTYPE html>...",  <- or build it with a Text step
//   "store": true,
//   "options": { "format": "A4" }
// }
//
// Response: { "document_url": "https://...", "document_id": "...", ... }
// Use document_url in the next step to email, Slack, or save to Drive.

Scheduled generation with GitHub Actions

For monthly reports or recurring documents, a cron schedule in GitHub Actions is often the simplest infrastructure. No server to maintain, no queue to run.

GitHub Actions
# .github/workflows/monthly-report.yml
# Runs on the 1st of every month to generate reports for all accounts

name: Monthly report generation

on:
  schedule:
    - cron: "0 6 1 * *"  # 06:00 UTC on the 1st

jobs:
  generate:
    runs-on: ubuntu-latest
    env:
      PDFPIPE_KEY: ${{ secrets.PDFPIPE_KEY }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: "20" }

      - run: npm ci
      - run: node scripts/generate-monthly-reports.js

Form submission → contract PDF (Python)

Generate a contract the moment a form is submitted. Verify the webhook, render the template, store the document, email the link.

Python / FastAPI
# FastAPI webhook handler
from fastapi import FastAPI, Header, Request, HTTPException
import httpx, os, hashlib, hmac

app = FastAPI()
PDFPIPE_KEY = os.environ["PDFPIPE_KEY"]

@app.post("/webhooks/form-submission")
async def on_form_submission(
    request: Request,
    x_webhook_secret: str = Header(None),
):
    # Verify the webhook sender
    if not hmac.compare_digest(x_webhook_secret or "", os.environ["FORM_WEBHOOK_SECRET"]):
        raise HTTPException(status_code=401, detail="Invalid webhook secret")

    body = await request.json()
    html = render_contract_template(body)  # fill your template

    async with httpx.AsyncClient(timeout=60) as client:
        res = await client.post(
            "https://api.pdfpipe.xyz/v1/pdf",
            headers={"Authorization": f"Bearer {PDFPIPE_KEY}"},
            json={"html": html, "store": True, "options": {"format": "Letter"}},
        )
        res.raise_for_status()
        data = res.json()

    await db.save_document(
        form_id=body["id"],
        pdf_id=data["document_id"],
        pdf_url=data["document_url"],
    )
    await send_email(body["email"], data["document_url"])
    return {"ok": True}

What you get back

When you pass store: true, the API returns JSON:

  • document_id: unique ID, also the storage key
  • document_url: time-limited public URL, ready to email or link
  • document_expires: epoch ms when the URL and file expire
  • size_bytes: PDF size
  • used / limit: current quota usage

Plans

PlanDocuments/moStoragePrice
Hobby5001 dayFree
Starter3,00030 days$19/mo
Growth15,000365 days$49/mo
Scale50,0002 years$149/mo
Business100,0002 years$499/mo

Try it free →