Anwendungsfall
Zertifikate per API erzeugen
Formfeed ist eine API, die aus einer Zertifikatsvorlage und einer Namensliste fertige Dokumente macht: Kurszertifikate, Teilnahmebescheinigungen, Urkunden und Diplome, als PDF zum Drucken und als PNG zum Teilen. Eine Anfrage rendert ein Zertifikat. Ein Batch rendert bis zu 1.000 als einen Job und liefert ein Zip. Die Vorlage ist HTML und CSS oder die Word- oder PowerPoint-Datei, die Sie schon haben.
Kostenloser Plan mit 100 Einheiten im Monat, keine Kreditkarte
Eine Anfrage, ein Zertifikat
Name, Kurs und Datum als JSON senden und das PDF zurückbekommen, oder dieselbe Vorlage mit output als PNG, JPG oder WebP. Die Antwort enthält einen signierten Download-Link oder mit ?output=binary die Datei selbst.
Ein ganzer Kurs als ein Job
Der Batch-Endpunkt nimmt bis zu 1.000 Zertifikate in einer Anfrage, rendert sie im Hintergrund und bündelt sie als ein Zip. Ein Webhook meldet Ihrem System, wenn der Job fertig ist.
Aus einer Tabelle, ohne Code
CSV- oder Excel-Datei in der App hochladen, die Spalten den Feldern der Vorlage zuordnen und das Zip herunterladen. Aus jeder Zeile wird ein Zertifikat.
Ein QR-Code zum Prüfen
Der Helfer qrcode druckt einen Link als QR-Code auf das Zertifikat, zum Beispiel die Adresse Ihrer Seite, die eine Zertifikatsnummer bestätigt.
Ihre Schriften
Google Fonts funktionieren mit dem üblichen link-Tag im Kopf der Vorlage. Ab dem Starter-Plan laden Sie eigene Schriftdateien einmal pro Organisation hoch, auch Schreib- und Display-Schriften, und sie werden in jedes Zertifikat eingebettet.
Jeder Name wird gedruckt
Gerendert wird mit Chromium und den Noto-Schriften hinter Ihren eigenen. Namen mit Akzenten und Namen in nicht-lateinischen Schriften, auch Chinesisch, Japanisch und Koreanisch, erscheinen so, wie sie gesendet wurden.
Ein PDF-Zertifikat aus Code erzeugen
Ein Zertifikat ist die Vorlage plus die Daten einer Person. Speichern Sie die Daten als data.json und senden Sie sie mit dem Namen der Vorlage; die Antwort enthält den Link zum PDF. TypeScript und Python nutzen die SDKs, jede andere Sprache schickt dasselbe JSON mit ihrer Standardbibliothek. Ein Schlüssel, der mit ff_test_ beginnt, rendert, ohne dass gezählt wird.
{
"name": "Jane Doe",
"course": "Analytical Engines 101",
"completed_on": "2026-09-10",
"hours": 16,
"instructor": "John Doe",
"id": "cert-8841",
"verify_url": "https://academy.example/verify/cert-8841"
}jq '{template: "certificate", data: .}' data.json \
| curl https://api-eu.formfeed.dev/v1/renders \
-H "Authorization: Bearer ff_test_..." \
-H "Content-Type: application/json" \
--data-binary @-import { Formfeed } from '@formfeed/sdk';
import data from './data.json' with { type: 'json' };
const formfeed = new Formfeed({ apiKey: process.env.FORMFEED_API_KEY! });
const render = await formfeed.renders.create({
template: 'certificate',
data,
});
console.log(render.status, render.download_url);import json
import os
from formfeed import Formfeed
formfeed = Formfeed(api_key=os.environ["FORMFEED_API_KEY"])
with open("data.json", encoding="utf-8") as file:
data = json.load(file)
render = formfeed.renders.create(
template="certificate",
data=data,
)
print(render.status, render.download_url)<?php
$body = [
'template' => 'certificate',
'data' => json_decode(file_get_contents('data.json'), true),
];
$curl = curl_init('https://api-eu.formfeed.dev/v1/renders');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('FORMFEED_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($body),
]);
$render = json_decode(curl_exec($curl), true);
echo $render['status'], ' ', $render['download_url'], PHP_EOL;package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
data, err := os.ReadFile("data.json")
if err != nil {
panic(err)
}
body, _ := json.Marshal(map[string]any{
"template": "certificate",
"data": json.RawMessage(data),
})
request, _ := http.NewRequest("POST", "https://api-eu.formfeed.dev/v1/renders", bytes.NewReader(body))
request.Header.Set("Authorization", "Bearer "+os.Getenv("FORMFEED_API_KEY"))
request.Header.Set("Content-Type", "application/json")
response, err := http.DefaultClient.Do(request)
if err != nil {
panic(err)
}
defer response.Body.Close()
var render struct {
Status string `json:"status"`
DownloadURL string `json:"download_url"`
}
json.NewDecoder(response.Body).Decode(&render)
fmt.Println(render.Status, render.DownloadURL)
}using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
var body = new
{
template = "certificate",
data = JsonDocument.Parse(File.ReadAllText("data.json")).RootElement,
};
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("FORMFEED_API_KEY"));
var response = await http.PostAsJsonAsync("https://api-eu.formfeed.dev/v1/renders", body);
var render = await response.Content.ReadFromJsonAsync<JsonElement>();
Console.WriteLine($"{render.GetProperty("status")} {render.GetProperty("download_url")}");import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
public class Render {
public static void main(String[] args) throws Exception {
String data = Files.readString(Path.of("data.json"));
String body = """
{
"template": "certificate",
"data": %s
}
""".formatted(data);
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api-eu.formfeed.dev/v1/renders"))
.header("Authorization", "Bearer " + System.getenv("FORMFEED_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body()); // status, download_url, page_count, units …
}
}require "json"
require "net/http"
body = {
template: "certificate",
data: JSON.parse(File.read("data.json")),
}
response = Net::HTTP.post(
URI("https://api-eu.formfeed.dev/v1/renders"),
JSON.generate(body),
"Authorization" => "Bearer #{ENV.fetch("FORMFEED_API_KEY")}",
"Content-Type" => "application/json"
)
render = JSON.parse(response.body)
puts render["status"], render["download_url"]Vollständige Anfrage- und Antwortformate in der Doku. Vollständiges Vorlagenbeispiel →
Das kommt heraus
Ein echtes Rendering der Beispielvorlage für diesen Anwendungsfall, direkt aus dem Render-Worker. Ein Klick öffnet die Datei.
Kostenloser Plan, keine Kreditkarte. Sie erhalten eine eigene Kopie im Editor, veröffentlicht und bereit für die API.
Zertifikate in Serie aus einer CSV-Datei
Ein Kurs endet, und zweihundert Menschen steht ein Zertifikat zu. Statt zweihundert Anfragen senden Sie eine: Jede Zeile der Liste wird ein Eintrag eines Batches, und der Batch ist ein Job. Formfeed rendert die Einträge im Hintergrund, und mit zip: true endet der Job in einem Archiv mit einem PDF pro Person, jedes unter dem Dateinamen, den Sie ihm gegeben haben, und in der Reihenfolge der Liste nummeriert: 0001-jane-doe.pdf, 0002-erika-mustermann.pdf.
curl https://api-eu.formfeed.dev/v1/renders/batch \
-H "Authorization: Bearer $FORMFEED_API_KEY" \
-H "Idempotency-Key: analytical-engines-101" \
-H "Content-Type: application/json" \
-d '{
"template": "certificate",
"zip": true,
"items": [
{
"filename": "jane-doe.pdf",
"data": {
"name": "Jane Doe",
"course": "Analytical Engines 101",
"completed_on": "2026-09-10",
"hours": 16,
"instructor": "John Doe",
"id": "cert-8841",
"verify_url": "https://academy.example/verify/cert-8841"
}
},
{
"filename": "erika-mustermann.pdf",
"data": {
"name": "Erika Mustermann",
"course": "Analytical Engines 101",
"completed_on": "2026-09-10",
"hours": 16,
"instructor": "John Doe",
"id": "cert-8842",
"verify_url": "https://academy.example/verify/cert-8842"
}
}
]
}'Die Antwort ist der Job. Fragen Sie GET /v1/jobs/{id} ab, bis sein Status completed ist, oder lassen Sie sich vom Webhook weiter unten benachrichtigen: zip_url ist dann das Archiv, und jedes Zertifikat hat zusätzlich einen eigenen Link. Ein Eintrag, der fehlschlägt, hält die anderen nicht auf; der Job endet als partially_failed und nennt die Einträge, die noch einmal zu senden sind. Eine Liste mit mehr als 1.000 Einträgen geht als mehrere Batches hinaus, und jedes Zertifikat zählt als eine Einheit, im Batch wie in einer einzelnen Anfrage.
Ein Webhook, wenn die Zertifikate fertig sind
Ein Batch dauert, so lange er dauert, und Ihr Code muss nicht darauf warten. Registrieren Sie einen Webhook-Endpunkt für den Workspace, und Formfeed sendet job.completed, wenn der Batch fertig ist, und render.completed für jedes Zertifikat, jeweils mit den Download-Links. Eine Batch-Anfrage kann auch eine eigene webhook_url nennen, an die die Ereignisse dieses Jobs zusätzlich gehen. Von dort verschickt Ihr System die E-Mail, legt das PDF beim Teilnehmer ab oder speichert es.
{
"id": "evt_5b0c8e6f2d4a4c3e9b1a7f6e5d4c3b2a",
"type": "job.completed",
"created_at": "2026-09-11T10:00:00Z",
"workspace_id": "3a6f…",
"data": {
"id": "job_5b2e8c1d9f3a4b6e8c0d2f4a6b8c0e1d",
"type": "batch",
"status": "completed",
"total": 200,
"succeeded": 200,
"failed": 0,
"zip_url": "https://cdn.formfeed.dev/…",
"items": [ … ]
}
}Zustellungen an einen registrierten Endpunkt sind signiert: Der Header Webhook-Signature trägt einen HMAC-SHA256 über Zeitstempel und Body, den Ihr Handler prüft, bevor er dem Ereignis glaubt; eine webhook_url aus der Anfrage wird signiert, wenn die Anfrage ein webhook_secret mitbringt. Eine Zustellung, die nicht innerhalb von zehn Sekunden mit 2xx beantwortet wird, wird bis zu fünfmal wiederholt, verteilt über etwa 14 Stunden und 40 Minuten.
Aus einem Formular oder einer Kursplattform, ohne Code
Nicht jedes Zertifikat beginnt in Ihrem eigenen Code. Ein Formular wird abgeschickt, eine Zeile kommt in eine Tabelle, jemand schließt einen Kurs in einem Werkzeug ab, das eine Automation starten kann: Der Formfeed-Schritt macht aus diesem Ereignis ein Zertifikat, und der nächste Schritt verschickt es.
Formfeed steht im App-Verzeichnis von Zapier. Die Aktion listet Ihre veröffentlichten Vorlagen und zeigt die Felder der gewählten mit Namen, sodass Sie name und course zuordnen, statt JSON zu schreiben. Die Dokumentation baut genau diesen Zap Schritt für Schritt: Aus einer Formularantwort wird ein Zertifikat, das per E-Mail hinausgeht. Wie es um Make, n8n und Power Automate steht, sagt die Integrationsseite.
Die Vorlage: HTML oder die Word- oder PowerPoint-Datei, die Sie schon haben
Eine Zertifikatsvorlage ist eine Seite aus HTML und CSS mit Platzhaltern in Jinja2, Liquid oder Handlebars: {{ name }} steht dort, wo der Name hingehört. Der Editor zeigt die Seite beim Schreiben so, wie sie gedruckt wird, im Querformat und bis an den Rand, gefüllt mit Ihren Beispieldaten. Das Zertifikatsbeispiel ist eine vollständige Vorlage zum Kopieren und Ändern.
Viele Zertifikate entstehen in PowerPoint oder Word. Ab dem Starter-Plan kann diese Datei die Vorlage sein: Setzen Sie dieselben Platzhalter in ihren Text, laden Sie sie hoch, und jedes Rendering füllt eine Kopie und gibt sie als PDF zurück. Mehr dazu unter Word- und PowerPoint-Vorlagen. Für Zertifikate als Bild setzt der visuelle Editor (Beta) den Namen und einen QR-Code auf eine Arbeitsfläche.
Was ein Zertifikat kostet
Ein Zertifikat mit bis zu fünf Seiten ist eine Einheit, als PDF wie als Bild. Der Free-Plan hat 100 Einheiten im Monat und verlangt keine Kreditkarte. Starter hat 3.000 Einheiten für 19 € im Monat, rund 0,6 Cent pro Zertifikat. Renderings mit einem Test-Schlüssel kosten nichts, solange Sie bauen, und ein fehlgeschlagenes Rendering wird nie gezählt. Alle Pläne stehen auf der Preisseite.
Was bei Ihnen bleibt
Formfeed erzeugt die Datei und gibt Ihnen ihren Link. Es verschickt Zertifikate nicht per E-Mail an die Genannten, es betreibt nicht die Seite, die eines bestätigt, und es ist keine Wallet für Nachweise: Die E-Mail geht aus Ihrem System oder aus einem Schritt Ihrer Automation hinaus, und der QR-Code zeigt auf eine Adresse von Ihnen. Dateien bleiben für die Aufbewahrung Ihres Plans erhalten, einen Tag im Free-Plan, 30 Tage bei Starter und 365 Tage bei Pro; speichern Sie also, was Sie länger brauchen. Ab dem Pro-Plan geht von jeder Datei beim Rendern eine Kopie in Ihren eigenen S3- oder R2-Bucket.
FAQ
- Gibt es ein SDK, um PDF-Zertifikate zu erzeugen?
- Ja:
@formfeed/sdkfür TypeScript und JavaScript undformfeedfür Python, beide mitrenders.createfür ein Zertifikat undrenders.batchfür eine Liste. Jede andere Sprache sendet dasselbe JSON über HTTPS; die Guides enthalten vollständige Programme für PHP, Go, C#, Java, Ruby und curl. - Wie viele Zertifikate passen in einen Batch?
- Bis zu 1.000 pro Anfrage; eine längere Liste geht als mehrere Batches hinaus. Jedes Zertifikat zählt als eine Einheit, und im Free-Plan braucht ein Live-Batch so viele freie Einheiten im Monat.
- Kann ein Zertifikat automatisch entstehen, wenn jemand einen Kurs abschließt?
- Ja, wenn die Kursplattform einen Webhook aufrufen oder eine Automation starten kann. Ihr Webhook erreicht einen Endpunkt von Ihnen, der die API aufruft, oder er startet einen Zap oder einen n8n-Workflow, in dem der Formfeed-Schritt steht. Wie es um Make und Power Automate steht, sagt die Integrationsseite.
- Wie prüfen Empfänger, ob ein Zertifikat echt ist?
- Über eine Seite von Ihnen. Der Helfer
qrcodedruckt deren Adresse mit der Zertifikatsnummer als QR-Code; Formfeed rendert den Code und betreibt die Seite dahinter nicht. - Funktionieren Namen mit Akzenten und nicht-lateinischen Schriften?
- Ja. Gerendert wird mit Chromium und Noto-Schriften für breite Schriftabdeckung, und mit Ihren eigenen Schriften, wenn Sie sie hochladen.
- Lässt sich das Zertifikat visuell gestalten?
- Für Zertifikate als Bild ja: Der visuelle Editor (Beta) setzt Name, Kurs und QR-Code auf eine Arbeitsfläche und bindet sie an Ihre Daten. Zertifikate als PDF entstehen als HTML im Code-Editor, wo sich ihr Wortlaut direkt in der Vorschau bearbeiten lässt, oder aus einer Word- oder PowerPoint-Datei.
