Code for an API certificate generator: QR layer, PDF vs PNG, 50-job batches. Positioning page: /use-cases/certificates.
Overview
Certificates are one of those things every product needs at some point — course completions, event participation, webinar attendance, professional credentials — and one of the most painful things to produce manually. A 500-student cohort means 500 personalized certificates: names, course titles, dates, unique IDs, all rendered on a branded template without typos and without a three-day design queue.
This post walks through building a certificate generator with jsontoimg — a JSON-in, image-out API. You design the certificate template once, connect your participant data, and every completion triggers a personalized, verifiable certificate automatically. By the end you’ll have a production pipeline that turns a database row into a finished certificate in seconds.
This is the implementation deep dive for our certificate generator guide — here we get into the actual code.
Why Manual Certificate Generation Breaks
Most organizations start with a designer, a template file, and a mail merge. It works for the first cohort of 20. It collapses at 500:
- Time: At 10–15 minutes per certificate (find record, edit name, export, proof), 500 graduates is 80–125 hours of work per cohort.
- Errors: Name misspellings, wrong course titles, and inconsistent dates slip through manual edits. A certificate with a typo is worse than no certificate — it gets screenshotted and shared.
- Latency: Participants expect their certificate immediately after completing a course, not after a “certificates will be sent within 3 business days” email.
- Verification: Unique certificate IDs and QR codes must be generated per recipient — impossible to do reliably by hand.
The fix is the same pattern used for product banners and OG images: a template rendered from data. Certificates are mail-merge for images.
The Data Model: One Row Per Certificate
Every certificate needs the same shape of data. Define it once as JSON, and it drives both the render and the verification record:
{
"certificate_id": "CERT-2026-004217",
"recipient_name": "Priya Sharma",
"course_title": "Advanced SQL for Data Analysts",
"instructor": "Balaji R",
"completion_date": "2026-08-01",
"duration_hours": 12,
"score_percent": 94,
"course_id": "sql-201",
"cohort": "Cohort 12"
}
Design your template with dynamic text layers for recipient_name, course_title, completion_date, and certificate_id. Everything else — logo, borders, signatures, background — stays static in the template. The API only swaps what’s dynamic.
Rendering a Single Certificate
With your template designed, rendering one certificate is a single POST request:
curl -X POST "https://app.jsontoimg.com/api/v1/render?wait=60" \
-H "Authorization: Bearer $JSONTOIMG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "certificate-award-v2",
"format": "png",
"layers": {
"certificate_id": { "text": "CERT-2026-004217" },
"recipient_name": { "text": "Priya Sharma" },
"course_title": { "text": "Advanced SQL for Data Analysts" },
"completion_date": { "text": "2026-08-01" }
}
}'
The response is your finished certificate at full print resolution — no browser, no HTML-to-image hacks, no font-loading surprises. If you need a lighter web preview, request output: "jpg" at a smaller width for the same template.
Batch Generation: 500 Certificates, One Request
For a cohort that just graduated, fire them all at once. jsontoimg accepts an array of data objects and returns an array of images:
curl -X POST https://app.jsontoimg.com/api/v1/renders/batch \
-H "Authorization: Bearer $JSONTOIMG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jobs": [
{
"template": "certificate-award-v2",
"format": "png",
"layers": {
"certificate_id": { "text": "CERT-2026-004217" },
"recipient_name": { "text": "Priya Sharma" },
"course_title": { "text": "Advanced SQL for Data Analysts" },
"completion_date": { "text": "2026-08-01" }
}
},
{
"template": "certificate-award-v2",
"format": "png",
"layers": {
"certificate_id": { "text": "CERT-2026-004218" },
"recipient_name": { "text": "Arjun Mehta" },
"course_title": { "text": "Advanced SQL for Data Analysts" },
"completion_date": { "text": "2026-08-01" }
}
}
]
}'
Your LMS or course platform already has this data — you’re just exporting a few columns. Here’s the whole pipeline in Python against a typical database:
import requests
import sqlite3
API_KEY = "YOUR_API_KEY"
TEMPLATE_ID = "certificate-award-v2"
conn = sqlite3.connect("lms.db")
rows = conn.execute("""
SELECT certificate_id, recipient_name, course_title, completion_date
FROM completions
WHERE certificate_generated = 0
""").fetchall()
jobs = [{
"template": TEMPLATE_ID,
"format": "png",
"layers": {
"certificate_id": { "text": r[0] },
"recipient_name": { "text": r[1] },
"course_title": { "text": r[2] },
"completion_date": { "text": r[3] },
},
} for r in rows[:50]]
resp = requests.post(
"https://app.jsontoimg.com/api/v1/renders/batch",
json={"jobs": jobs},
headers={"Authorization": f"Bearer {API_KEY}"},
)
batch = resp.json()
print(batch["id"], batch.get("acceptedCount"))
That’s the whole generator: select ungenerated completions → render → save. Run it after every cohort closes, or on a schedule.
Async Delivery with Webhooks
For very large batches — an event with 10,000 attendees, a certification body with quarterly exam cycles — synchronous rendering ties up your request for too long. Use async mode with a webhook instead:
curl -X POST https://app.jsontoimg.com/api/v1/renders/batch \
-H "Authorization: Bearer $JSONTOIMG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jobs": [ /* max 50; each job has template + layers; optional webhookUrl per job */ ]
}'
# Then GET /api/v1/renders/batches/{id}
jsontoimg calls your webhook when each batch finishes, and your app emails the certificate or updates the participant record. The typical flow:
Webhook trigger (course completion)
→ Look up participant row
→ Render certificate (sync for singles, batch for cohorts)
→ Upload to storage / attach to email
→ Mark certificate_generated = 1
Verification: Unique IDs and QR Codes
A certificate without verification is just a nice picture. Put the certificate_id on the certificate itself, and optionally a QR code that resolves to a public verification page:
- Generate a unique ID per recipient in your database (
CERT-2026-004217). - Pass it into the render as a dynamic layer — it appears on the certificate.
- Publish a public endpoint:
https://verify.yoursite.com/CERT-2026-004217shows “Valid — issued to Priya Sharma for Advanced SQL for Data Analysts.” - Render the same ID as a QR code layer so employers can scan and verify instantly.
This is the same pattern event platforms use for attendee badges and event tickets: unique identifier in, tamper-evident image out.
Two of the choices above have published guidance behind them. For the identifier, RFC 9562 is the current UUID specification and the reason to mint a v4 or v7 UUID rather than an incrementing integer: a sequential certificate number tells a holder how many you have issued and lets anyone guess a neighbour’s URL. For legibility, WCAG 2.1’s contrast minimum applies to a certificate as much as to a web page, and a recipient name set in light grey on a cream background fails it on every copy. The format call is the last one — MDN’s image file type guide covers PNG for the screen-shared version, while PDF is what survives being printed.
Real-World Numbers
An online learning platform with 5 concurrent courses, 100 graduates each per month:
| Step | Manual | jsontoimg pipeline |
|---|---|---|
| Design template | 1 designer, ~4 hours | 1 designer, ~4 hours (once) |
| Generate 500 certificates | 80–125 hours | ~2 minutes of API time |
| Error rate | Typos happen per-certificate | Zero — data in, image out |
| Delivery time | 3 days processing | Minutes after completion |
| Verification | None or manual lookup | Built-in IDs + QR |
The template cost is paid once; the marginal cost of the 501st certificate is a single API call.
Key Takeaways
- Certificates are a data problem, not a design problem — one template plus JSON per recipient is the whole system.
- Batch rendering handles entire cohorts in one request; webhooks make it async for events at any scale.
- Unique IDs and QR codes turn generated certificates into verifiable credentials.
- The same pipeline pattern applies across use cases: product banners, OG images, tickets, welcome graphics.
FAQ
How do I generate hundreds of certificates at once?
One row per recipient, then POST /api/v1/renders/batch in chunks of 50. Five hundred certificates is ten sequential batch requests.
Can the certificates come out as PDF instead of PNG?
Yes. Send format: "pdf" and a multi-page template becomes a multi-page PDF, billed one credit per page — the same as a still.
How do I make a generated certificate verifiable?
Mint a unique ID per recipient, render it into the certificate as text, and point a QR image layer at a verification URL containing that ID. The image becomes a lookup key rather than a claim.
Do I need a webhook for a certificate run?
For a cohort of a few dozen, waiting on the batch response is fine. Past a few hundred, pass a webhook so the run does not hold a connection open while the queue drains.
Related Resources
- Building a Certificate Generator with a Template-to-Image API — the use case overview
- Getting Started with jsontoimg: Your First API Call — template basics before you build
- Auto-Generate Open Graph Images for Your Blog — another data-to-image pipeline
- Event Ticket Generation with jsontoimg — unique-ID patterns for per-person assets
- Certificate generator API guide
Zoom out: How JSON-to-Image Conversion Works maps the whole template, named-layer, and render-job architecture.