Use case
Receipt PDF API
Generate purchase receipts, subscription-renewal confirmations, and refund receipts from HTML with a single POST request. Built for high volume: one clean document for every transaction, no SDK and no browser to run.
Built for one receipt per transaction
Every charge, renewal, and refund can fire a single POST and get a PDF back. The endpoint is stateless, so you can fan out concurrent calls from a payment webhook without queuing or rate-limit gymnastics.
Save the template, send only data
Store your receipt design once and reference it by template_id. On each transaction you POST a compact data object instead of full HTML, which keeps payloads small and your render logic in one place.
Your brand, pixel for pixel
Send any HTML you already write for confirmation emails. The renderer runs a full browser, so CSS Grid, Flexbox, web fonts, and gradients render exactly as they do in preview. No proprietary template language to learn.
Refund on failure
If a render times out or fails, the document is not counted against your quota. Safe to retry inside an idempotent webhook handler without burning credits.
Store and email a link
Add store: true to get a stable download URL instead of bytes. Drop it into the order-confirmation or renewal email as a download link. Retention follows your plan (1 day free, 30 days Starter, 1 year Growth and up).
Refund and renewal receipts too
The same endpoint generates refund confirmations and subscription-renewal receipts. Swap the line items and total, change one heading, and the output matches the rest of your customer documents.
1. Write the HTML template
Any HTML template engine works. Here is a Handlebars template for a typical receipt: brand mark, a paid marker, the order number, who was billed, line items, and the total charged.
<!-- templates/receipt.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style>
body { font-family: system-ui, sans-serif; color: #1a1a1a; margin: 0; padding: 40px; }
.header { display: flex; justify-content: space-between; align-items: start; }
.brand { font-size: 22px; font-weight: 700; }
.paid { font-size: 12px; font-weight: 700; letter-spacing: 1px; color: #1a7f37; }
.meta { text-align: right; font-size: 13px; color: #666; }
.title { font-size: 26px; font-weight: 700; margin: 28px 0 4px; }
.order { font-size: 13px; color: #888; }
.ship-to { margin: 24px 0; font-size: 13px; }
table { width: 100%; border-collapse: collapse; margin: 28px 0; }
th { text-align: left; padding: 8px 12px; background: #f5f5f5; font-size: 12px; }
td { padding: 10px 12px; border-bottom: 1px solid #eee; font-size: 13px; }
.num { text-align: right; }
.total-row td { font-weight: 700; border-top: 2px solid #1a1a1a; border-bottom: none; }
.footer { font-size: 12px; color: #888; margin-top: 40px; }
</style>
</head>
<body>
<div class="header">
<div class="brand">Acme Store</div>
<div class="meta">
<div class="paid">PAID</div>
<div>{{date}}</div>
</div>
</div>
<div class="title">Receipt</div>
<div class="order">Order #{{orderNumber}}</div>
<div class="ship-to">
<div style="font-size:12px;color:#888;margin-bottom:4px">Billed to</div>
<div>{{customer.name}}</div>
<div>{{customer.email}}</div>
</div>
<table>
<thead>
<tr><th>Item</th><th class="num">Qty</th><th class="num">Unit price</th><th class="num">Amount</th></tr>
</thead>
<tbody>
{{#each items}}
<tr>
<td>{{description}}</td>
<td class="num">{{quantity}}</td>
<td class="num">${{unitPrice}}</td>
<td class="num">${{amount}}</td>
</tr>
{{/each}}
</tbody>
<tfoot>
<tr class="total-row">
<td colspan="3">Total charged</td>
<td class="num">${{total}}</td>
</tr>
</tfoot>
</table>
<div class="footer">
This receipt confirms a completed payment. Keep it for your records.
Questions about this order? Reply to your confirmation email.
</div>
</body>
</html>2. Render and POST (Node.js)
Call this from the place that confirms a payment, typically a billing or payment webhook handler, so every successful charge produces a receipt.
// Node.js: render the receipt and call the API on every successful charge
import Handlebars from "handlebars";
import { readFileSync } from "fs";
const tmpl = Handlebars.compile(readFileSync("./templates/receipt.html", "utf-8"));
export async function generateReceiptPdf(data: {
orderNumber: string;
date: string;
customer: { name: string; email: string };
items: { description: string; quantity: number; unitPrice: string; amount: string }[];
total: string;
}): Promise<Uint8Array> {
const html = tmpl(data);
const resp = await fetch("https://api.pdfpipe.xyz/v1/pdf", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PDFPIPE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
html,
options: {
format: "A4",
margin: { top: "16mm", bottom: "16mm", left: "16mm", right: "16mm" },
print_background: true,
},
}),
});
if (!resp.ok) throw new Error(`Receipt generation failed: ${resp.status}`);
return new Uint8Array(await resp.arrayBuffer());
}
// Call it from your payment webhook handler, e.g. on charge.succeeded2. Render and POST (Python)
# Python: Jinja2 template + httpx, triggered from your billing event handler
import httpx
from jinja2 import Environment, FileSystemLoader
jinja = Environment(loader=FileSystemLoader("templates"))
def generate_receipt_pdf(data: dict) -> bytes:
html = jinja.get_template("receipt.html").render(**data)
resp = httpx.post(
"https://api.pdfpipe.xyz/v1/pdf",
headers={"Authorization": f"Bearer {settings.PDFPIPE_API_KEY}"},
json={
"html": html,
"options": {
"format": "A4",
"margin": {"top": "16mm", "bottom": "16mm", "left": "16mm", "right": "16mm"},
"print_background": True,
},
},
timeout=60.0,
)
resp.raise_for_status()
return resp.contentSave the template, send only the data
For high volume you do not want to ship the full HTML on every transaction. Store the receipt design once, then reference it with template_id and pass a compact data object on each call. Combine it with store: true to get a download URL back for the confirmation email.
// Use a saved template: store the receipt design once, call with data every time.
// Ideal for high volume: you send only the order data, not the full HTML, on each charge.
const resp = await fetch("https://api.pdfpipe.xyz/v1/pdf", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PDFPIPE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
template_id: "tmpl_your_receipt_template_id",
data: {
orderNumber: "ORD-2026-8841",
date: "June 13, 2026",
customer: { name: "Priya Sharma", email: "priya@example.com" },
items: [
{ description: "Pro Plan (monthly)", quantity: 1, unitPrice: "49.00", amount: "49.00" },
],
total: "49.00",
},
store: true,
filename: "receipt-ORD-2026-8841.pdf",
}),
});
const { document_url } = await resp.json();
// Send document_url in the order-confirmation emailPricing
| Plan | Price | Receipts / month | Overage |
|---|---|---|---|
| Hobby | Free | 500 | Not available |
| Starter | $19 | 3,000 | Not available |
| Growth | $49 | 15,000 | Not available |
| Scale | $149 | 50,000 | Not available |
| Business | $499 | 100,000 | Contact sales |
Start generating receipts
500 receipts a month free. No credit card. Key issued instantly after signup.