How to Generate PDF Receipts in Indian Languages with PDFShift
Build localized receipt HTML, load a script-compatible font, and convert it with PDFShift. Includes runnable examples and a checklist for validating the PDF.
To generate a PDF receipt in an Indian language with PDFShift, create the receipt as HTML, choose a font that includes the required script glyphs, and submit the raw HTML as the request’s source. Define the font with CSS using a locally hosted font file or embedded base64 font data where practical. Then inspect the actual PDF using representative receipt text before shipping: a font choice alone does not guarantee correct shaping or output for every language and viewer.
This guide uses PDFShift’s documented raw HTML conversion endpoint and custom CSS font support. Raw HTML avoids having PDFShift fetch a separate receipt page, and inline styles can reduce external requests. PDFShift notes that locally hosted and base64 fonts can load more consistently than external fonts. Raw HTML conversion guide · Custom fonts guide.
1. Prepare the receipt HTML and font
Keep receipt data separate from presentation, then render it into a complete HTML document. Use the actual localized values for seller details, item names, tax labels, dates, and totals. The sample below uses generic Devanagari text and a placeholder font URL. Replace that URL with a font file you are authorized to use and that covers your target script; do not assume this sample font URL exists or supports a particular language.
<!doctype html>
<html lang="hi">
<head>
<meta charset="utf-8">
<title>Receipt</title>
<style>
@font-face {
font-family: "ReceiptDevanagari";
src: url("https://YOUR_PUBLIC_HOST/fonts/your-script-font.woff2") format("woff2");
font-weight: 400;
font-style: normal;
}
@page { size: A5; margin: 14mm; }
body {
font-family: "ReceiptDevanagari", sans-serif;
color: #171717;
font-size: 12pt;
line-height: 1.5;
}
h1 { font-size: 18pt; margin: 0 0 12px; }
.row { display: flex; justify-content: space-between; gap: 16px; }
table { width: 100%; border-collapse: collapse; margin-top: 16px; }
th, td { border-bottom: 1px solid #bbb; padding: 7px 4px; text-align: left; }
.amount { text-align: right; }
.total { margin-top: 16px; font-weight: bold; }
</style>
</head>
<body>
<h1>रसीद</h1>
<div class="row"><span>रसीद संख्या</span><span>R-10042</span></div>
<div class="row"><span>दिनांक</span><span>२०२६-१०-०४</span></div>
<p>विक्रेता: उदाहरण स्टोर</p>
<table>
<thead><tr><th>विवरण</th><th class="amount">राशि</th></tr></thead>
<tbody>
<tr><td>उत्पाद</td><td class="amount">₹ 500.00</td></tr>
<tr><td>कर</td><td class="amount">₹ 90.00</td></tr>
</tbody>
</table>
<p class="total">कुल: ₹ 590.00</p>
</body>
</html>
lang="hi" describes this example’s language; set it to the appropriate language tag for the receipt. HTML’s UTF-8 declaration helps interpret Unicode text correctly. It does not supply missing glyphs. Escape any user-provided values when inserting them into HTML, and avoid building markup by concatenating untrusted input. For mixed-script receipts, define a suitable fallback stack or use spans with script-specific font families. The chosen font must cover every script, numeral style, punctuation mark, and currency symbol that appears.
2. Choose how to deliver the font
| Method | When it fits | Trade-off |
|---|---|---|
| External font URL | Quick prototyping or a font already hosted publicly | Depends on external network access and font loading; PDFShift warns external font loading may be intermittent. |
| Locally hosted font URL | Production conversion where you control a stable public font asset | The conversion service must be able to reach the URL. Version and retain the asset used for each template. |
| Base64 embedded font | Self-contained HTML where eliminating a separate font fetch is useful | Increases request body size and requires correct encoding and CSS font format declaration. |
PDFShift’s font guide presents local font URLs and base64 encoded fonts as more consistent options than relying on an external font URL. That guidance concerns loading reliability; it is not a guarantee that a font shapes every Indian script correctly. Confirm licensing permits embedding and server-side document generation.
3. Convert raw HTML with PDFShift
Keep the API key on a server or in a secret manager. The examples below send the HTML as JSON and save the response bytes as a PDF. They use the documented PDFShift endpoint, https://api.pdfshift.io/v3/convert/pdf, and the X-API-Key header. Set PDFSHIFT_API_KEY in the process environment before running them. These examples require a local file named receipt.html containing the markup above.
cURL
export PDFSHIFT_API_KEY='sk_your_api_key'
python3 -c 'import json, pathlib; pathlib.Path("request.json").write_text(json.dumps({"source": pathlib.Path("receipt.html").read_text(encoding="utf-8")}))'
curl --fail-with-body --silent --show-error \
-X POST 'https://api.pdfshift.io/v3/convert/pdf' \
-H "X-API-Key: $PDFSHIFT_API_KEY" \
-H 'Content-Type: application/json' \
--data-binary @request.json \
-o receipt.pdf
The small Python command prepares valid JSON so characters in the HTML are escaped correctly. Delete request.json after conversion if it contains personal receipt data.
Python
import os
from pathlib import Path
import requests
api_key = os.environ["PDFSHIFT_API_KEY"]
html = Path("receipt.html").read_text(encoding="utf-8")
response = requests.post(
"https://api.pdfshift.io/v3/convert/pdf",
headers={"X-API-Key": api_key},
json={"source": html},
timeout=90,
)
response.raise_for_status()
if not response.content.startswith(b"%PDF-"):
raise RuntimeError(f"Expected PDF bytes; got: {response.text[:500]}")
Path("receipt.pdf").write_bytes(response.content)
Install the dependency with python -m pip install requests. The explicit signature check helps catch unexpected non-PDF responses before storing them with a .pdf extension.
Node.js
import { readFile, writeFile } from "node:fs/promises";
const apiKey = process.env.PDFSHIFT_API_KEY;
if (!apiKey) throw new Error("Set PDFSHIFT_API_KEY first");
const html = await readFile("receipt.html", "utf8");
const response = await fetch("https://api.pdfshift.io/v3/convert/pdf", {
method: "POST",
headers: {
"X-API-Key": apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify({ source: html }),
signal: AbortSignal.timeout(90_000),
});
if (!response.ok) {
throw new Error(`PDFShift returned ${response.status}: ${(await response.text()).slice(0, 500)}`);
}
const pdf = Buffer.from(await response.arrayBuffer());
if (pdf.subarray(0, 5).toString() !== "%PDF-") {
throw new Error("The response did not contain a PDF");
}
await writeFile("receipt.pdf", pdf);
4. Validate the output with real receipt data
- Use representative text for each supported language, including real names, product labels, tax names, and punctuation. Include mixed Latin and Indian-script text if receipts use both.
- Open the PDF in the viewers your customers use. Inspect conjuncts, combining marks, vowel signs, numeral forms, punctuation, and currency symbols at normal zoom and when printed.
- Check text selection and extraction when search, copying, or downstream processing matters. A visually plausible PDF may still have unexpected text extraction.
- Review wrapping, column alignment, totals, and page breaks with long item names and the largest expected receipt. Try empty optional fields and unusually large amounts.
- Repeat after changing the font file, CSS, conversion options, or template. Keep a known-good sample PDF and a small set of representative text cases for release review.
These are validation recommendations: the available PDFShift documentation explains font delivery and conversion, but does not establish blanket support for every script, glyph sequence, shaping behavior, or viewer. Validate your target language and data end to end.
5. Options, reliability, privacy, and cost
Raw HTML or a hosted source URL?
Raw HTML is useful for personalized or private receipt content and avoids a separate fetch for the source page. PDFShift also documents URL-based conversion, which can fit an already hosted page. With a URL, the converter must be able to access the page and its assets. Inline CSS and scripts, where suitable, reduce external dependencies. Keep any required font asset reachable or embed it.
Make conversion predictable
- Use a stable, versioned font asset and template. Do not rely on a font URL that can change without your release process noticing.
- Reduce external images, stylesheets, scripts, and font requests when they are not needed for the receipt.
- Set a finite client timeout and handle non-success responses. For transient network or service errors, use bounded retries with backoff; avoid blindly retrying malformed HTML, invalid credentials, or other permanent errors.
- For high-volume issuance, record a receipt identifier and your conversion outcome so a retry does not accidentally issue duplicate business events. PDF conversion and payment/receipt issuance are separate operations.
- Keep the API key out of browser JavaScript. PDFShift’s integration guidance warns against exposing a secret key in front-end calls. PDFShift Make integration guide.
Receipt data and storage
PDFShift’s FAQ says standard conversions do not store requests or generated documents, while its documentation describes storage when options such as filename are used. The current data processing agreement says submitted HTML is processed transiently by default and that generated output stored through the filename functionality may be kept for up to two days. Verify the current terms and exact request mode for your data before sending personal or financial receipt details. Avoid optional hosted-file or webhook workflows if you do not want the generated output stored there; consider your own storage destination if needed. PDFShift data processing agreement.
Cost and conversion size
The research reviewed for this guide records PDFShift’s FAQ as saying one credit covers a conversion up to 5 MB and giving a Boost plan example of $24 per month for 2,500 credits; it also records 50 free credits per month for new accounts. These are time-sensitive vendor terms, not durable guarantees. Check PDFShift’s current pricing and plan details before estimating production cost. Measure the final request and generated document sizes, especially if embedding fonts or images, and account for retries and expected monthly receipts. PDFShift pricing.
6. Troubleshooting
| Symptom | Likely cause | What to check |
|---|---|---|
| Characters appear as empty boxes or disappear | Font lacks glyph coverage, font file failed to load, or fallback lacks the script | Verify the font URL is reachable by the conversion service; inspect CSS and font response; use a font with required glyphs and retest the actual text. |
| Characters render separately or marks look misplaced | Font or rendering path does not produce the expected shaping for that text | Try another suitable font and validate the actual language sample and PDF viewer. Do not assume a successful conversion means correct shaping. |
| Font works on one run but not another | External font request is intermittent, asset changed, or resource access differs | Prefer a stable locally hosted font URL or base64 font data; version the asset and retry only transient failures. |
| HTML text is garbled | Incorrect character encoding or broken JSON escaping | Read the template as UTF-8, include the charset declaration, and serialize JSON with a library rather than hand-escaping it. |
| API returns an error instead of a PDF | Missing/invalid key, malformed request, or service-side failure | Check the status and response body; verify the X-API-Key header and JSON content type. Do not save an error response as a PDF. |
| Conversion stalls or times out | Slow external resources or a complex document | Use raw HTML, inline essential CSS, remove unnecessary network assets, and set a bounded timeout. Retry only plausibly transient failures. |
| Text is clipped or totals move to another page | Content exceeds layout assumptions or page dimensions | Test long labels and amounts, adjust CSS and page size/margins, and inspect the full output rather than only the first page. |
| API key appears in browser tools or page source | Conversion is being called directly from front-end code | Move the call to a backend and keep the key in server-side secret storage. |
7. A screenshot API for checking receipt previews
PDFShift generates the PDF. If your workflow also needs a screenshot of the HTML receipt preview—for a review step, archive, or visual comparison—a screenshot API is a separate tool for that task. ScreenshotNeo is a website screenshot API and MCP server; it does not replace PDFShift’s PDF conversion.
Or skip the browser setup
For a website receipt preview, ScreenshotNeo can capture a URL with one request. This example captures a preview page as WebP; see the ScreenshotNeo API documentation for options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo removes cookie/consent banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Sign up for 1,000 free screenshots a month, with no card.
FAQ
Does selecting a font guarantee correct output for every Indian language?
No. The font must cover the script, and the actual generated PDF must be checked for shaping and viewer behavior. PDFShift’s reviewed documentation does not claim universal script support.
Can I submit receipt HTML without publishing it?
Yes. PDFShift documents raw HTML in the source parameter, so the receipt page does not need to be publicly hosted. Keep the API key on your backend.
Should I use an external font URL?
It can work, but PDFShift warns external fonts may load intermittently. A reachable local font URL or base64 font data can reduce that dependency.
Can I use this flow to make a screenshot of the receipt?
PDFShift’s flow here returns a PDF. For a screenshot of a web preview, use a screenshot tool such as ScreenshotNeo separately.


