QES für Plattformen mit vielen Signaturen je Person
Ihre Plattform lässt Personen mit eigenem qualifiziertem Zertifikat viele Dokumente signieren – Bestätigungen, Bescheinigungen, Verträge –, oft Dutzende am Tag und ohne dass die Person bei jeder Unterschrift vor dem Bildschirm sitzt. Dieser Guide beschreibt den Weg mit automatischen Zertifikaten: einmal einrichten, danach signiert Ihr System per API, und zwar nur im Rahmen der Einwilligung, die die Person selbst erteilt hat.
Grundlagen zu Mitgliedern, Zertifikaten und Freigabe-Links stehen im Guide „QES mit Mitgliedern und Freigabe-Link“. Mit einem Test-Schlüssel (qgw_test_…) läuft jeder Schritt im Testmodus, ohne Kosten.
Überblick
- Mitglied anlegen – die Person, die signiert.
- Automatisches Zertifikat bestellen; die Person identifiziert sich einmal.
- Einwilligung zum automatischen Signieren anfragen; die Person legt den Rahmen fest.
- Zugangsdaten hinterlegen und testen.
- Signieren mit
certificateId– einzeln oder im Stapel. - Ergebnis per Webhook empfangen, signiertes Dokument und Nachweis abrufen.
1. Mitglied anlegen
POST /v1/members
Authorization: Bearer qgw_test_…
Idempotency-Key: mitglied-0815
Content-Type: application/json
{ "firstName": "Erika", "lastName": "Mustermann", "email": "erika.mustermann@example.com", "externalId": "person-0815" }
externalId ist Ihre Kennung der Person, damit Sie sie später zuordnen. Scope members:manage.
2. Automatisches Zertifikat bestellen
POST /v1/certificates
Authorization: Bearer qgw_test_…
Idempotency-Key: zertifikat-0815
Content-Type: application/json
{ "memberId": "0192f1b0-0000-7000-8000-00000000010f", "kind": "AUTOMATIC" }
Scope certificates:purchase. Die Antwort enthält identificationUrl: Die Person identifiziert sich einmalig über diesen Link (sie bekommt ihn auch per E-Mail). Den Stand zeigt GET /v1/certificates/{id} (lifecycleState). Warten Sie hier nicht auf certificate.activated: Dieses Ereignis kommt erst nach dem erfolgreichen Test der Zugangsdaten in Schritt 4, der wiederum die Einwilligung aus Schritt 3 voraussetzt. Fragen Sie die Einwilligung gleich nach der Bestellung an.
3. Einwilligung anfragen
Ein automatisches Zertifikat signiert mit certificateId nur, wenn die Person zugestimmt hat. Ihre Plattform fragt die Einwilligung an, erteilen kann sie nur die Person selbst:
POST /v1/certificates/0192f1b0-0000-7000-8000-00000000010e/consent-request
Authorization: Bearer qgw_test_…
Idempotency-Key: einwilligung-0815
Content-Type: application/json
{ "scope": { "channels": ["API", "BATCH"], "apiKeyIds": ["0192f1b0-0000-7000-8000-000000000201"], "dailyLimit": 200, "validUntil": "2027-12-31T23:00:00Z" } }
channels:APIfür einzelne Signaturen perPOST /v1/signatures,BATCHfür Stapel perPOST /v1/signature-batches. Nennen Sie nur die Kanäle, die Ihre Plattform nutzt.apiKeyIds(optional) beschränkt die Einwilligung auf die Schlüssel Ihrer Plattform.dailyLimit(optional): höchstens so viele Signaturen je Kalendertag (Berliner Zeit). Planen Sie das Limit mit Abstand zum üblichen Tagesvolumen.validUntil(optional): Ablaufdatum der Einwilligung.
Die Antwort ist immer 202 (object: automatic_consent_request). Die Person bekommt eine E-Mail mit einem persönlichen Link (72 Stunden gültig), erteilt oder lehnt dort ab und kann die Einwilligung jederzeit widerrufen oder sofort sperren. Änderungen meldet das Ereignis certificate.consent_changed (Scope certificates:read). Eine neue Anfrage ersetzt eine offene.
4. Zugangsdaten hinterlegen und testen
PUT /v1/certificates/{id}/credentials speichert Benutzername und PIN des Zertifikats verschlüsselt – nur, solange eine gültige Einwilligung besteht (sonst 403 / 9006 automatic_consent_missing). Danach prüft POST /v1/certificates/{id}/test die Zugangsdaten mit einer Testsignatur; ist der Test erfolgreich, ist das Zertifikat bereit und das Ereignis certificate.activated folgt. Im Testmodus hinterlegt POST /v1/certificates/{id}/sandbox-credentials die Zugangsdaten der Sandbox. Ab jetzt nennt Ihr System nur noch die certificateId; die PIN muss es nicht aufbewahren.
5. Signieren
Einzelnes Dokument
POST /v1/signatures
Authorization: Bearer qgw_test_…
Idempotency-Key: bestaetigung-0815
Content-Type: application/json
{
"inputDocuments": [{ "sourceType": "base64", "payload": "<PDF als Base64>", "name": "bestaetigung-0815.pdf" }],
"certificateId": "0192f1b0-0000-7000-8000-00000000010e",
"signatureType": "pades",
"options": { "level": "B" },
"placement": { "page": 1, "x": 391, "y": 36, "width": 168, "height": 39 },
"appearance": { "showDate": true, "reason": "Bestätigung 0815" },
"externalId": "bestaetigung-0815",
"metadata": { "vorgang": "0815" }
}
certificateIdwählt das Zertifikat. Benutzername und Passwort im Body (certificateUsername,certificatePassword) sind abgekündigt; neue Integrationen senden sie nicht.- Ein Aufruf mit API-Schlüssel ist der Kanal
API: Die Einwilligung muss ihn abdecken (und den Schlüssel, wenn sieapiKeyIdsnennt). Sonst antwortet die API mit 403 / 9013 unddetails.reason(automatic_consent_missing,automatic_channel_not_allowed,automatic_blocked) und signiert nichts; über dem Tageslimit mit 409 / 9016automatic_daily_limit. - Base64-Dokumente sind bis 11 MB (Länge des Base64-Texts) möglich. Größere PDFs bis 50 MB laden Sie vorher mit
POST /v1/documentsoderPOST /v1/documents/importhoch und sendendocumentIdstattinputDocuments. placementundappearanceerzeugen eine sichtbare Signatur (Seite, Rechteck in PDF-Punkten, Datum mit Zeitzone der Organisation, Grund bis 120 Zeichen); ohneplacementist die Signatur unsichtbar. Damit alles lesbar bleibt, wählen Sie das Rechteck mindestens so groß: nur Datum 140 × 40, Datum und Grund bis 40 Zeichen 180 × 60, Grund bis 120 Zeichen 260 × 80 PDF-Punkte (Einzelheiten im Guide „QES mit Mitgliedern und Freigabe-Link“, Abschnitt „Sichtbares Signaturbild“). Passt der Grund trotzdem nicht, wird er im Bild gekürzt und die Signatur meldetoptions.appearance.reasonTruncated: true(Antwort,GET /v1/signatures/{id}, Webhooks – auch für jedes Dokument eines Stapels); in der Signatur selbst steht er vollständig.externalIdundmetadatasind Ihre Referenzen: Sie stehen an der Signatur, kommen in jeder Antwort und jedem Webhook zurück und lassen sich filtern (GET /v1/signatures?externalId=bestaetigung-0815). An den Signaturdienst gehen sie nie.
Ungültige Dokumente und Ablehnungen
- Ungültige Dokumente werden vor jedem Signaturversuch abgewiesen: 422 / 9012 mit
details.reason(invalid_base64,empty,not_pdf,unparseable) und bei mehreren Dokumentendetails.index. Dabei entstehen weder Kosten noch ein Datensatz; korrigieren Sie das Dokument und senden Sie es neu. - Verschlüsselte PDFs gehen als Ganzes an den Signaturdienst; ist zum Öffnen ein Kennwort nötig, senden Sie es in
options.encryptionPassword. Lehnt der Dienst ein verschlüsseltes PDF ohneoptions.encryptionPasswordab, antwortet die API 422 / 9019 mitdetails.reason: encrypted. Eine sichtbare Signatur (placement) lässt sich in ein verschlüsseltes PDF nicht einfügen: ohneoptions.encryptionPassword422 / 9012encrypted. - Lehnt der Signaturdienst eine Anfrage ab, antwortet die API mit 422 / 9019 und
details.reason(signature_refused,timestamp_failed,encryptedoderrejected). Dasselbe Wort steht danach alserror.reasonan der Signatur (GET /v1/signatures/{id},signature.failed) – auch wenn die Ablehnung erst im Hintergrund kommt, etwa bei einem Stapel oder nach einer 202-Antwort. - Fällt der Signaturdienst aus, nachdem die Anfrage gesendet wurde (Fehler, Zeitüberschreitung oder Verbindungsabbruch), ist das Ergebnis endgültig: synchron antwortet die API 503 / 9010 mit
QGW-Should-Retry: true, die Signatur steht danach aufREJECTEDmiterror.reason: service_failedunderror.shouldRetry: true. Kommt die Störung erst im Hintergrund (nach einer 202-Antwort), meldetsignature.faileddasselbe, wenn bis etwa zehn Minuten nach dem Senden kein Ergebnis eingetroffen ist. Senden Sie das Dokument dann erneut als neue Anfrage mit einem neuenIdempotency-Key.
Antwort, Wartezeit und Wiederholungen
- Der Aufruf antwortet in der Regel nach wenigen Sekunden mit 200 und
status: DONE. Ist die Signatur nach 25 Sekunden nicht fertig (gezählt ab dem Eingang; die Vorbereitung großer Dokumente zählt mit und wird nicht abgebrochen) oder ist der Signaturdienst ausgelastet, antwortet die API mit 202 undstatus: WAIT_SIGNund dem HeaderRetry-After; die Signatur läuft weiter und wird von selbst abgeschlossen – das Ergebnis kommt per Webhook (signature.completedodersignature.failed), percallbackund überGET /v1/signatures/{id}. Es wird nie ein zweites Mal signiert. - Setzen Sie das Timeout Ihres HTTP-Clients auf mindestens 30 Sekunden. Der erste Aufruf nach längerer Ruhe kann länger dauern als die folgenden.
- Mit
Prefer: respond-asyncbekommen Sie immer sofort 202 – sinnvoll, wenn Ihre Plattform viele Dokumente nacheinander schickt und nicht auf jede Signatur warten will. - Dauerhaft je API-Schlüssel (Dashboard → Entwickler → API-Schlüssel → „Zustellung ändern“, Inhaberin oder Admin):
asyncDefaultbeantwortet jede Signaturanfrage des Schlüssels sofort mit 202;priority: bulk(„Massenversand“) reiht zusätzlich nach den interaktiven Anfragen ein und nutzt nur einen begrenzten Teil der Kapazität (nur absenkbar, zurück zuinteractivegeht nur mit einem neuen Schlüssel);expiresSecondsDefault(60 bis 86.400 Sekunden) ist die Frist in der Warteschlange, wenn die Anfrage keinexpiresInSecondsnennt.GET /v1/mezeigt die drei Werte unterapiKey. Anfragen mitcertificateOtpwerden nie eingereiht. - Senden Sie immer einen
Idempotency-Key(zum Beispiel Ihre Dokumentnummer). Eine Wiederholung mit gleichem Schlüssel und gleichem Body liefert 24 Stunden lang die gespeicherte Antwort statt einer zweiten Signatur. 409 / 9008 heißt: Die erste Anfrage läuft noch – kurz warten und mit demselben Schlüssel wiederholen. Derselbe Schlüssel mit anderem Body antwortet 422 / 9008. - Den Stand liest
GET /v1/signatures/{id}; öfter als alle paar Sekunden abzufragen beschleunigt nichts. Besser: Webhook (Schritt 6).
Viele Dokumente auf einmal: Stapel
Laden Sie die Dokumente hoch (POST /v1/documents/import mit base64 oder fileUrl) und signieren Sie sie in einem Aufruf:
POST /v1/signature-batches
Authorization: Bearer qgw_test_…
Idempotency-Key: stapel-2026-11-03
Content-Type: application/json
{
"certificateId": "0192f1b0-0000-7000-8000-00000000010e",
"title": "Bescheinigungen 3. November",
"placement": { "page": "last", "x": 380, "y": 60, "width": 168, "height": 39 },
"appearance": { "showDate": true, "reason": "Bescheinigung" },
"documents": [
{ "documentId": "0192f1b0-0000-7000-8000-000000000103", "title": "Bescheinigung 0815" },
{ "documentId": "0192f1b0-0000-7000-8000-000000000104", "title": "Bescheinigung 0816" }
]
}
Die Antwort kommt sofort (202) mit einer Signatur-ID je Dokument; signiert wird im Hintergrund. Ein Stapel ist der Kanal BATCH und zählt jedes Dokument gegen das Tageslimit der Einwilligung. Wenn alles fertig ist, kommt signature_batch.completed; GET /v1/signature-batches/{id}/archive liefert alle signierten Dokumente als ZIP. Stapel müssen für Ihre Organisation freigeschaltet sein (sonst 403 / 9023).
Bis zu 250 Dokumente mit einer Freigabe
Ein Stapel nimmt bis zu 250 Dokumente (die Grenze Ihres Tarifs; Standard 50, mehr auf Anfrage) und wird mit einem Aufruf freigegeben, im Dashboard mit einer Bestätigung. Mehr als die Grenze: 422 / 9012 batch_too_large mit details.max.
- Vorabprüfung vor der 202: Reicht das Tageskontingent Ihrer Organisation für Stapel-Dokumente oder die Signaturkapazität für heute nicht für den ganzen Stapel, kommt 422 / 9012 mit
details.reasonbatch_budgetbzw.batch_reserve,details.rejected(budget,reserve),details.requestedunddetails.acceptable(wie viele noch passen); angelegt wird nichts. Das Kontingent zählt alle Stapel des Tages, auch offene – viele kleine Stapel umgehen es nicht. Mit"allowPartial": truenimmt der Aufruf stattdessen die erstenacceptableDokumente an (202, Feldpartial: { requestedItems, acceptedItems, rejected }); die übrigen senden Sie später erneut. Der monatliche Kostendeckel gilt weiter für den ganzen Stapel (403 / 9017). - Reihenfolge: Einzelne Signaturen gehen vor Stapeln; Stapel teilen sich die Kapazität im Wechsel je Organisation und je Zertifikat (höchstens einige Dokumente eines Zertifikats gleichzeitig). Richtwert: ein Stapel mit 250 Dokumenten ist im Normalbetrieb nach etwa 5 Minuten fertig, in Spitzenzeiten nach bis zu 15 Minuten.
- Fortschritt: Ihr Organisations-Webhook bekommt
signature_batch.progressalle 25 fertigen Dokumente bzw. alle 10 % (der größere Schritt; bei 250 Dokumenten also neunmal), je Stand höchstens einmal, mitdoneCount,failedCount,returnedCount,openCountundfinishedCount. Am Ende kommt wie bishersignature_batch.completedmit den Teilerfolgen;GET /v1/signature-batches/{id}zeigt jederzeit den Stand. - Tagesfrist: Ist für Ihre Installation eine Tagesfrist gesetzt (z. B. 23:30 Uhr), trägt der Stapel sie als
dailyDeadlineAt. Dokumente, die bis dahin nicht an den Signaturdienst gesendet wurden, werden unsigniert zurückgegeben statt verspätet signiert: ZustandREJECTEDmit 9009,signature.failedgenau einmal, gezählt infailedCountundreturnedCount, ohne Kosten. Ein Stapel, der nach der Frist kommt, wird mit 422 / 9012batch_daily_deadlineabgelehnt (details.deadline). Kontingent, Kapazitätsprüfung, Ablehnung nach der Frist undallowPartialgelten für alle Stapelarten (Dokumente, Einmal-Codes, Aufgaben; gezählt werden die Positionen); offene Positionen von Stapeln mit Einmal-Codes oder Aufgaben folgen ihrer Freigabe bzw. dem Vorgang und werden zur Frist nicht zurückgegeben. - Kosten: Jedes signierte Dokument wird genau einmal berechnet; zurückgegebene, abgebrochene und gescheiterte Dokumente nicht. Im Testmodus entstehen keine Kosten.
6. Ergebnis empfangen
Ein Webhook-Abo meldet jedes Ergebnis, ohne dass Ihre Plattform abfragt:
POST /v1/webhook-subscriptions
Authorization: Bearer qgw_test_…
Content-Type: application/json
{ "url": "https://example.com/hooks/signatures", "events": ["signature.completed", "signature.failed", "signature_batch.completed"] }
signature.completed enthält Ihre externalId und metadata sowie Links auf das signierte Dokument und den Nachweis. Abrufen können Sie beides auch direkt:
GET /v1/signatures/{id}/content– das signierte PDF (mit?format=base64als JSON),GET /v1/signatures/{id}/audit-trail– das Nachweisprotokoll (JSON oder PDF überAccept); es nennt die signierende Person, den Kanal, den API-Schlüssel (Name und Präfix), das Zertifikat und bei automatischen Zertifikaten die Einwilligung, die beim Signieren galt – das PDF auf Deutsch und Englisch mit Ortszeit,POST /v1/signatures/{id}/download-links– ein Download-Link ohne API-Schlüssel, 15 Minuten gültig.
Wie Sie die Signatur der Webhooks prüfen, zeigt der Guide „Webhooks empfangen und prüfen“.
Hinweise
- Live-Signaturen beachten das monatliche Ausgabenlimit Ihrer Organisation (403 / 9017 bei hartem Limit).
- Im Live-Modus braucht die Einwilligung einen freigegebenen Einwilligungstext; bis dahin antwortet die Anfrage mit 409 / 9030.
- Zertifikate mit Einmalcodes (
kind: OTP) eignen sich nicht für diesen Weg: Jede Signatur braucht den aktuellen Code der Person (certificateOtp), ohne ihn 422 / 9025otp_required. Für solche Zertifikate gibt es den Freigabe-Link (Guide „QES mit Mitgliedern und Freigabe-Link“).