Signatur-API: Integration mit Base44
Oeffentliche Dokumentation: api.malymi.de. Die Markdown-Version fuer die Uebergabe an Base44 steht unter api.malymi.de/download zum Download bereit. Diese Domain dient nur der Dokumentation; die API-Basis steht im folgenden Abschnitt.
Zweck und Betriebsstand
Die API verwendet die bestehende einfache elektronische Signatur des Vertrags-Tools: handschriftlich gezeichnete Unterschrift oder Namenskuerzel mit ausdruecklicher Zustimmung. Es handelt sich nicht um eine qualifizierte elektronische Signatur und nicht um eine kryptografische PAdES-Signatur. Das Protokoll dokumentiert den Vorgang und die SHA-256-Pruefsummen, bietet aber keinen qualifizierten Zeitstempel oder WORM-Speicher.
API-Basis:
https://contracts.novastack-solutions.com/api/v1
Alle Requests sind serverseitig aus Base44 auszufuehren. API-Key und Webhook-Secret gehoeren in die Backend-Secrets, niemals in Browser-Code, oeffentliche Entities oder URLs. Es gibt absichtlich keine CORS-Freigabe fuer Browser-Aufrufe.
Der Betreiber legt pro Integration einen API-Client an und uebergibt API-Key und Webhook-Secret einmalig auf sicherem Weg. Es existiert kein Standard-API-Key. Die Webhook-Ziel-URL wird serverseitig hinterlegt; sobald die oeffentliche HTTPS-URL des Base44-Webhook-Endpunkts feststeht, muss sie dem Betreiber mitgeteilt werden. Bis dahin werden Ereignisse nicht an Base44 zugestellt (siehe Wiederholungen).
Ablauf
- Base44 erzeugt das PDF und kennt die exakten Signaturpositionen.
- Base44 laedt PDF und JSON-Metadaten mit einem eindeutigen Idempotency-Key hoch.
- Die API persistiert Original, Vorschau, Unterzeichner und Versandauftraege.
- Der Worker verschickt jedem Unterzeichner einen persoenlichen Link. Die API-Antwort auf den Upload bedeutet noch nicht, dass eine E-Mail verschickt wurde.
- Unterzeichner sehen das Dokument, ihren von Base44 gelieferten Namen und eine Liste ihrer Felder. Eine ausdruecklich bestaetigte Signatur wird in alle diesem Unterzeichner zugeordneten Felder eingesetzt.
- Nach allen Unterschriften erzeugt der Worker das finale PDF und das PDF-Protokoll.
- Erst nach erfolgreicher Speicherung wird
request.completedmit Downloadlinks verschickt. Base44 laedt das finale PDF herunter und ordnet es ueberexternal_idzu.
Unterstuetzt werden 1 bis 10 Unterzeichner, parallel und ohne festgelegte Reihenfolge.
Alle Felder sind Pflichtfelder vom Typ signature. Optionale Felder, separate
Unterschriften pro Feld, Ablehnungen, Erinnerungen, OTP und qualifizierte Signaturen
sind nicht Teil dieser Version. Die bestehenden manuell erstellten Vertraege bleiben
unveraendert und sind nicht automatisch ueber die neue API abrufbar.
Authentifizierung
Authorization: Bearer CLIENT_ID.SECRET
Jeder Client sieht nur seine eigenen Vorgaenge. Ein deaktivierter Client kann weder API, Signaturlinks noch Ergebnis-Downloadlinks verwenden.
Vorgang anlegen
POST /signature-requests
Authorization: Bearer CLIENT_ID.SECRET
Idempotency-Key: base44-vertrag-4711-version-1
Content-Type: multipart/form-data; boundary=...
Multipart-Teile:
| Name | Inhalt |
|---|---|
pdf |
Binaere PDF-Datei, maximal 20 MiB |
metadata |
JSON-Objekt als Text, maximal 128 KiB |
Beispiel fuer metadata:
{
"external_id": "base44-vertrag-4711",
"title": "Dienstleistungsvertrag",
"sender_name": "Musterfirma GmbH",
"expires_in_days": 7,
"signers": [
{
"id": "kunde",
"name": "Max Mustermann",
"email": "max@example.com"
}
],
"fields": [
{
"id": "unterschrift_kunde",
"signer_id": "kunde",
"type": "signature",
"page": 3,
"x": 70,
"y": 620,
"width": 180,
"height": 60,
"required": true
}
]
}
Pflichtangaben und Feldformat
external_id: Pflicht, Base44-interne Zuordnung, maximal 180 Zeichen. Nicht automatisch eindeutig; unterschiedliche Dokumentversionen koennen dieselbe Referenz haben.title: Pflicht, maximal 180 Zeichen, wird in Einladung und Signierseite angezeigt.sender_name: Pflicht, maximal 180 Zeichen, sichtbarer Absendername und Grussformel. Die Absender-E-Mail-Adresse stammt ausschliesslich aus der bestehenden serverseitigen Mailkonfiguration. Base44 kann keine beliebige From-Adresse oder HTML-Mail vorgeben.expires_in_days: ganze Zahl von 1 bis 30, Standard 7, ab Erstellung des Vorgangs.signers[].id: eindeutige ID innerhalb des Vorgangs, 1 bis 64 Zeichen aus Buchstaben, Ziffern,_oder-.signers[].name: Pflicht, maximal 180 Zeichen; persoenliche Anrede und verbindlicher Name im Protokoll. Im Signierformular nicht editierbar.signers[].email: gueltige E-Mail-Adresse, maximal 254 Zeichen.fields: 1 bis 100 strukturierte Felddefinitionen; keine optische Erkennung, keine Textanker und kein Auslesen benannter AcroForm-Felder.fields[].id: eindeutige Feldbezeichnung innerhalb des Vorgangs, maximal 64 Zeichen.fields[].signer_id: muss auf einen gelieferten Unterzeichner verweisen.- Jeder Unterzeichner braucht mindestens ein Feld.
page: ganze Zahl, erste Seite ist 1.x,y,width,height: Zahlen in PDF-Punkten, 72 Punkte = 1 Zoll. Ein Millimeter entspricht72 / 25.4Punkten.- Ursprung ist die linke obere Ecke der sichtbaren, gedrehten Seite (CropBox).
xwaechst nach rechts,ynach unten. Bei einem PDF mit Seitenrotation gelten die Abmessungen so, wie die Seite im PDF-Viewer erscheint. xundysind mindestens 0, Breite und Hoehe mindestens 10 Punkte. Das gesamte Rechteck muss innerhalb der angegebenen Seite liegen.- Die Unterschrift wird proportional skaliert und mittig im Rechteck platziert. Es werden keine zusaetzlichen Namen oder Datumszeilen ausserhalb des Felds eingefuegt.
requireddarf entfallen oder musstruesein;falsewird abgewiesen.- Nicht dokumentierte Metadaten-, Unterzeichner- und Feldattribute werden abgewiesen.
PDF: 1 bis 100 Seiten, unverschluesselt und ohne Passwort. Der freie FPDI-Parser
unterstuetzt keine komprimierten Cross-Reference-/Objektstreams aller PDF-Versionen;
bei unsupported_pdf bitte als unverschluesseltes PDF 1.4 ohne Objektstreams exportieren.
Formularinhalte muessen vor dem Upload in sichtbaren Seiteninhalt umgewandelt
(flattened) sein. Interaktive Widgets, Links, Anhaenge und vorhandene kryptografische
Signaturen werden beim Import nicht uebernommen. Keine bereits kryptografisch
unterschriebenen PDFs fuer diesen Ablauf verwenden. Das Original bleibt unveraendert
gespeichert; Vorschau und finale Datei sind neu aufgebaute PDFs mit importiertem Seiteninhalt.
Idempotenz
Idempotency-Key ist Pflicht, 8 bis 128 Zeichen aus A-Z a-z 0-9 _ . : -.
Gleicher Client + gleicher Key + gleiche validierte Metadaten und PDF-Bytes liefert
denselben Vorgang zurueck, ohne erneut Einladungen anzulegen. Bei veraenderten Daten
folgt HTTP 409. Der Key gilt fuer die Lebensdauer des gespeicherten Vorgangs.
Nach einem Netzwerkfehler muss Base44 mit demselben Key und Inhalt wiederholen.
Die Reihenfolge der JSON-Eigenschaften ist unerheblich; die Reihenfolge der
Unterzeichner und Felder in den Arrays bleibt Teil des Inhaltsvergleichs.
Beispiel mit curl
curl --fail-with-body \
-H "Authorization: Bearer $SIGNATURE_API_KEY" \
-H "Idempotency-Key: base44-vertrag-4711-version-1" \
-F "pdf=@vertrag.pdf;type=application/pdf" \
-F "metadata=<metadata.json" \
https://contracts.novastack-solutions.com/api/v1/signature-requests
HTTP 201 bei Erstellung (mit Location-Header auf den neuen Vorgang),
HTTP 200 bei idempotenter Wiederholung. Antwort:
{
"id": "sig_0123456789abcdef0123456789abcdef",
"external_id": "base44-vertrag-4711",
"title": "Dienstleistungsvertrag",
"sender_name": "Musterfirma GmbH",
"status": "pending",
"created_at": "2026-10-20T10:00:00Z",
"expires_at": "2026-10-27T10:00:00Z",
"original_sha256": "...",
"pages": [
{ "page": 1, "width": 595.28, "height": 841.89 }
],
"fields": [],
"signers": [
{
"id": "kunde",
"name": "Max Mustermann",
"email": "max@example.com",
"status": "pending",
"invitation_status": "queued"
}
]
}
pages und fields enthalten in der echten Antwort alle Seiten und gelieferten Felder.
Persoenliche Signaturlinks und private Protokolldaten sind nicht in Statusantworten enthalten.
Status und Downloads
GET /signature-requests/{id}
GET /signature-requests/{id}/document
GET /signature-requests/{id}/audit
Alle drei Endpunkte verlangen den Bearer-Key. Die letzten beiden liefern PDF-Dateien,
aber nur bei completed; vorher HTTP 409.
| Status | Bedeutung |
|---|---|
pending |
Angelegt, noch keine Unterschrift |
partially_signed |
Mindestens eine, aber noch nicht alle Unterschriften gespeichert |
finalizing |
Alle Unterschriften gespeichert, PDF-Erstellung ausstehend |
completed |
Finales PDF und Protokoll erfolgreich gespeichert |
document_failed |
PDF-Erstellung fehlgeschlagen, Worker versucht es erneut |
expired |
Frist ohne vollstaendige Unterzeichnung abgelaufen |
cancelled |
Durch API abgebrochen |
Felder der Statusantwort (identisch fuer Upload, Status-GET, Abbruch und Webhook-data):
| Feld | Inhalt |
|---|---|
id, external_id, title, sender_name |
Vorgangsdaten |
status |
Vorgangsstatus, siehe Tabelle oben |
created_at, expires_at |
Zeitpunkte, ISO 8601 in UTC |
completed_at |
nur bei completed |
cancelled_at |
nur bei cancelled |
original_sha256 |
SHA-256 des hochgeladenen Original-PDFs |
pages[] |
page, width, height in PDF-Punkten (sichtbare Seite) |
fields[] |
normalisierte Felddefinitionen wie geliefert |
signers[] |
id, name, email, status, invitation_status, optional viewed_at, signed_at |
document, audit |
nur bei completed: download_url, expires_at, sha256 |
Optionale Felder fehlen, solange sie keinen Wert haben.
signers[].status: pending oder signed.
invitation_status: queued, sent, retrying oder failed.
sent bedeutet Annahme durch den Mailanbieter, nicht Postfachzustellung oder Lesen.
Bei completed enthaelt die Statusantwort zusaetzlich:
{
"completed_at": "2026-10-20T11:00:00Z",
"document": {
"download_url": "https://contracts.novastack-solutions.com/api/v1/downloads/sig_.../document?expires=...&signature=...",
"expires_at": "2026-10-20T12:00:00Z",
"sha256": "..."
},
"audit": {
"download_url": "https://contracts.novastack-solutions.com/api/v1/downloads/sig_.../audit?expires=...&signature=...",
"expires_at": "2026-10-20T12:00:00Z",
"sha256": "..."
}
}
Diese signierten Links benoetigen keinen Bearer-Key und sind daher wie Zugangsdaten
zu behandeln. Links aus Statusantworten gelten 1 Stunde, Links aus Webhooks 7 Tage.
Neue Links liefert jederzeit GET /signature-requests/{id}. Downloadlinks nicht als
dauerhafte Dokumentadresse speichern. Base44 sollte die Datei serverseitig herunterladen,
den SHA-256-Hash pruefen und in seinem geschuetzten Dateispeicher ablegen.
Der Bearer-Download ist alternativ ohne kurzlebigen Link moeglich.
Abbrechen
POST /signature-requests/{id}/cancel
Ohne Request-Body. Antwort ist die Statusantwort mit status: "cancelled".
Nur fuer pending und partially_signed; wiederholter Abbruch liefert denselben
Status. Sonst HTTP 409 invalid_state. Bereits gespeicherte Teilunterschriften bleiben als Nachweis erhalten,
aber weitere Unterzeichnungen und finale Downloads werden verhindert.
finalizing, completed, expired und document_failed koennen nicht abgebrochen werden.
Webhooks
Eine feste Ziel-URL und ein eigenes Webhook-Secret werden pro API-Client serverseitig konfiguriert. Eine beliebige URL im Upload wird nicht akzeptiert. Erlaubt ist ausschliesslich oeffentliches HTTPS auf Port 443; keine Weiterleitungen, internen IPs oder HTTP-Basic-Zugangsdaten in der URL.
Ereignisse:
| Typ | Bedeutung |
|---|---|
request.created |
Vorgang gespeichert |
invitation.sent |
Mailanbieter hat Einladung angenommen |
invitation.failed |
Versandversuch fehlgeschlagen |
request.viewed |
Signierseite erstmals je Unterzeichner aufgerufen |
signer.signed |
Unterschrift eines Unterzeichners gespeichert |
request.completed |
Finale Dateien verfuegbar |
document.failed |
Finalisierung fehlgeschlagen |
request.expired |
Ablauf gespeichert (der Worker erkennt abgelaufene Vorgaenge automatisch, etwa minuetlich) |
request.cancelled |
Abbruch gespeichert |
Ein Seitenaufruf ist kein Nachweis persoenlicher Kenntnisnahme; auch Linkscanner
koennen request.viewed ausloesen.
Content-Type: application/json
X-Signature-Timestamp: 1792494000
X-Signature-Signature: v1=HEX_HMAC_SHA256
X-Signature-Event-Id: evt_0123456789abcdef0123456789abcdef
{
"event_id": "evt_0123456789abcdef0123456789abcdef",
"type": "request.completed",
"occurred_at": "2026-10-20T11:00:00Z",
"request_id": "sig_0123456789abcdef0123456789abcdef",
"external_id": "base44-vertrag-4711",
"signer_id": null,
"data": {
"id": "sig_0123456789abcdef0123456789abcdef",
"status": "completed",
"document": {
"download_url": "https://contracts.novastack-solutions.com/api/v1/downloads/...",
"expires_at": "2026-10-27T11:00:00Z",
"sha256": "..."
},
"audit": {
"download_url": "https://contracts.novastack-solutions.com/api/v1/downloads/...",
"expires_at": "2026-10-27T11:00:00Z",
"sha256": "..."
}
}
}
data ist die Statusantwort zum Ereigniszeitpunkt, einschliesslich weiterer oben
beschriebener Felder. Bei empfaengerbezogenen Ereignissen ist signer_id gesetzt.
Verifikation und Zustellung
Signierter Inhalt ist:
TIMESTAMP + "." + UNVERAENDERTER_HTTP_BODY
HMAC-SHA256 mit dem Webhook-Secret, hexadezimal und mit Praefix v1=.
Den Body vor JSON-Parsing als Text/Bytes lesen. Zeitstempel mit maximal
5 Minuten Abweichung pruefen und die Signatur in konstanter Zeit vergleichen.
Den Header X-Signature-Event-Id mit event_id aus dem signierten Body abgleichen.
Ereignisse werden mindestens einmal zugestellt, nicht garantiert in zeitlicher
Reihenfolge. Base44 muss event_id dauerhaft und eindeutig speichern und bereits
verarbeitete Ereignisse mit 2xx bestaetigen. Ereignisse schnell und dauerhaft
annehmen, dann die eigentliche Verarbeitung im Backend ausfuehren. Ein blosses
2xx vor dauerhafter Speicherung kann Ereignisse verlieren.
Erfolg ist jeder HTTP-Status 200 bis 299. Timeout nach 15 Sekunden. Nach Fehlern
erfolgen bis zu 7 Wiederholungen nach etwa 1, 5, 15, 60, 180, 360 und 720 Minuten
(jeweils Abstand zum vorherigen Versuch, plus Worker-Takt). Jede Zustellung hat
einen neuen Signaturzeitstempel; event_id und Body bleiben gleich.
Nach 8 Fehlversuchen wird der Auftrag als failed gespeichert.
Mailversand und Finalisierung verwenden dieselben Wiederholungsabstaende.
Nach einem Prozessabbruch ist auch ein erneuter Mailversand moeglich.
Wegen moeglicher verzoegerter Ereignisse nicht blind alte Statuswerte ueber neue schreiben: Bei Bedarf den aktuellen Status per GET abgleichen. Ein Webhook-Fehler veraendert den Abschlussstatus eines Dokuments nicht.
Zustellprobleme abrufen und erneut versuchen
GET /signature-requests/{id}/deliveries
POST /signature-requests/{id}/retry-deliveries
Antwort: { "deliveries": [...] }, mit id, kind (email, webhook, finalize),
state (pending, running, delivered, failed), attempts, due_at
(Unix-Sekunden), last_error und created_at. Maximal 500 Eintraege pro Vorgang.
delivered kann bei nicht mehr erforderlichen Mailauftraegen auch bedeuten, dass
sie ohne Versand abgeschlossen wurden, z.B. nach Abbruch oder bereits erfolgter Signatur.
POST (ohne Body) setzt alle ausgeschoepften failed-Auftraege dieses Vorgangs zurueck
und liefert danach dieselbe deliveries-Liste.
Bereits wartende Auftraege behalten ihre geplante Wiederholung.
Sehr alte Webhook-Downloadlinks koennen beim manuellen Replay abgelaufen sein;
in diesem Fall neue Links per Status-GET abrufen.
Base44-Backend: Upload-Beispiel
Eigenen Webhook in Base44 anlegen
Ein Base44-Projekt ist nicht auf einen einzigen Endpunkt begrenzt. Jede
veroeffentlichte Backend-Funktion erhaelt einen eigenen HTTP-Endpunkt.
Fuer diese Integration eine eigene Funktion signatureWebhook anlegen.
Laut aktueller Base44-Entwicklerdokumentation lautet das URL-Schema:
https://DEINE-APP-DOMAIN/functions/signatureWebhook
Die tatsaechlich fuer euer Projekt bereitgestellte URL aus der Base44-Oberflaeche uebernehmen; nicht allein aus der Frontend-URL ableiten. Unter Dashboard > API zeigt der Bereich Backend Functions die aufrufbaren Funktionen. Die normale App-Startseite bleibt bestehen; andere Integrationen erhalten weitere eigene Funktionen und Endpunkte.
Die Funktion muss unseren POST-Request ohne interaktiven Benutzer-Login annehmen,
zuerst die HMAC-Signatur pruefen und erst danach Daten lesen/schreiben.
Base44-Aufrufe ueber direkte HTTP-Webhooks haben keinen angemeldeten Benutzer;
falls asServiceRole benoetigt wird, ausschliesslich innerhalb dieser
serverseitigen Funktion und nach erfolgreicher Signaturpruefung verwenden.
Geheime Werte nur serverseitig speichern.
Moeglicher Auftrag an den Base44-Editor:
Erstelle eine Backend-Funktion signatureWebhook fuer externe POST-Webhooks
unserer Signatur-API. Verwende den unveraenderten Request-Body und pruefe
X-Signature-Timestamp (maximal 5 Minuten Abweichung) und
X-Signature-Signature (v1=HMAC-SHA256 ueber timestamp + "." + body)
gegen das Backend-Secret SIGNATURE_WEBHOOK_SECRET.
Gleiche X-Signature-Event-Id mit event_id im signierten Body ab.
Akzeptiere nur korrekt signierte Requests, ohne Benutzer-Login zu verlangen.
Speichere verifizierte Ereignisse dauerhaft und dedupliziere nach event_id;
bestaetige erst danach mit 2xx. Ordne sie ueber external_id und request_id
dem passenden Vorgang zu. Bei request.completed liegen die Downloadlinks
unter data.document.download_url und data.audit.download_url.
Lade Dateien serverseitig herunter und pruefe die gelieferten SHA-256-Hashes.
Gib mir nach dem Deployment die tatsaechliche oeffentliche HTTPS-URL der Funktion.
Nutze fuer den vollstaendigen Vertrag die beigefuegte API.md.
Offizielle Referenzen: Backend Functions, HTTP-Endpunkte und API-Seite im App-Dashboard.
PDF aus einer Backend-Funktion hochladen
Dies ist plattformneutraler Fetch-Code fuer eine serverseitige Funktion. Wie Secrets, PDF-Dateizugriff, Aufruferberechtigungen und Webhook-Routen in eurem konkreten Base44-Projekt eingebunden werden, muss dort passend verdrahtet werden.
async function submitSignatureRequest({ pdfBytes, metadata, apiKey, versionKey }) {
const form = new FormData();
form.append("pdf", new Blob([pdfBytes], { type: "application/pdf" }), "vertrag.pdf");
form.append("metadata", JSON.stringify(metadata));
const response = await fetch(
"https://contracts.novastack-solutions.com/api/v1/signature-requests",
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Idempotency-Key": versionKey
},
body: form
}
);
const result = await response.json();
if (!response.ok) {
throw new Error(`${response.status}: ${JSON.stringify(result.error)}`);
}
return result; // id und external_id im Base44-Vorgang speichern
}
Content-Type fuer FormData nicht manuell setzen; Fetch ergaenzt die Boundary.
Vor dem Aufruf die Base44-Berechtigung fuer den betreffenden Vorgang pruefen.
Webhook-HMAC mit Web Crypto pruefen
async function verifySignatureWebhook(request, webhookSecret) {
const rawBody = await request.text();
const timestamp = request.headers.get("X-Signature-Timestamp") ?? "";
const signature = request.headers.get("X-Signature-Signature") ?? "";
if (!/^\d+$/.test(timestamp)
|| Math.abs(Date.now() / 1000 - Number(timestamp)) > 300
|| !/^v1=[a-f0-9]{64}$/.test(signature)) {
throw new Error("Invalid webhook timestamp or signature format");
}
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
"raw", encoder.encode(webhookSecret),
{ name: "HMAC", hash: "SHA-256" }, false, ["verify"]
);
const bytes = Uint8Array.from(
signature.slice(3).match(/../g), pair => parseInt(pair, 16)
);
const valid = await crypto.subtle.verify(
"HMAC", key, bytes, encoder.encode(`${timestamp}.${rawBody}`)
);
if (!valid) throw new Error("Invalid webhook signature");
const event = JSON.parse(rawBody);
if (event.event_id !== request.headers.get("X-Signature-Event-Id")) {
throw new Error("Invalid event ID");
}
return event;
}
Der Webhook muss von unserem Server ohne Base44-Benutzersitzung erreichbar sein. Seine Authentifizierung erfolgt ueber die obige HMAC-Pruefung, nicht durch eine oeffentlich freigegebene Service-Role oder einen API-Key im Querystring.
Fehlerformat
{
"error": {
"code": "invalid_fields",
"message": "Signaturfeld liegt ausserhalb der Seite: unterschrift_kunde"
}
}
| HTTP | Beispiele |
|---|---|
| 401 | Fehlender, falscher oder deaktivierter API-Key |
| 403 | Falscher/abgelaufener Downloadlink |
| 404 | Vorgang fehlt oder gehoert zu anderem Client |
| 405 | Falsche HTTP-Methode |
| 409 | Idempotenzkonflikt oder unpassender Vorgangsstatus |
| 415 | Kein Multipart-Upload |
| 422 | Ungueltige Metadaten, PDF, Felder oder Uploadfehler |
| 500 | Interner Fehler; Details nur im Serverlog |
| 503 | API noch nicht konfiguriert |
Maschinenlesbare error.code-Werte (fuer Programmlogik verwenden, message nur anzeigen):
| HTTP | code |
Ursache |
|---|---|---|
| 401 | unauthorized |
Bearer-Key fehlt, falsch oder Client deaktiviert |
| 403 | invalid_download |
Signierter Downloadlink falsch oder abgelaufen |
| 404 | not_found |
Endpunkt/Vorgang nicht vorhanden oder fremder Client |
| 405 | method_not_allowed |
Falsche HTTP-Methode |
| 409 | idempotency_conflict |
Idempotency-Key bereits mit anderen Daten verwendet |
| 409 | not_completed |
Dokument/Protokoll vor completed angefordert |
| 409 | invalid_state |
Abbruch in diesem Status nicht moeglich |
| 415 | unsupported_media_type |
Kein multipart/form-data |
| 422 | invalid_idempotency_key |
Header fehlt oder Format ungueltig |
| 422 | upload_failed |
PDF-Teil fehlt oder Upload abgebrochen/zu gross |
| 422 | invalid_metadata |
Metadaten-JSON, Unterzeichner oder Feldangaben ungueltig |
| 422 | invalid_pdf |
Keine PDF-Datei, ueber 20 MiB oder nicht 1 bis 100 Seiten |
| 422 | unsupported_pdf |
PDF verschluesselt oder vom Parser nicht lesbar |
| 422 | invalid_fields |
Feld ausserhalb der Seite oder Seite nicht vorhanden |
| 500 | internal_error |
Interner Fehler |
| 503 | not_configured |
API noch nicht konfiguriert |
Webserver/Proxy koennen uebergrosse Requests bereits vor PHP mit 413 abweisen. Netzwerkfehler und 5xx beim Anlegen mit gleichem Idempotency-Key wiederholen. 4xx anhand Fehlercode korrigieren, nicht unveraendert endlos wiederholen.
Betrieb und Einrichtung
Voraussetzungen: PHP 8.2 mit curl, gd, mbstring, pdo_sqlite und den Composer-
Abhaengigkeiten aus composer.lock. Apache benoetigt mod_rewrite und AllowOverride All.
.htaccess schuetzt interne Dateien und routet /api/v1/...; .user.ini enthaelt
die Upload-Limits fuer FPM. Bei einem anderen Webserver muessen diese Regeln
explizit uebernommen werden. Standard-PHP-Limits von 2 MiB reichen nicht aus.
cd /var/www/vertrags-signatur-tool
composer install --no-dev --prefer-dist --no-interaction
sudo php manage_api.php init
sudo php manage_api.php create-client base44 \
https://DEIN-BASE44-BACKEND/DEIN-WEBHOOK \
/root/base44-signature-credentials.json
init nicht erneut ausfuehren, wenn bereits eingerichtet.
Das Credentials-File ist nur fuer root lesbar. Die beiden Secrets sicher in die
Base44-Backend-Secrets uebertragen und die Transferdatei danach sicher entfernen.
Es werden keine Secrets in der Konsole ausgegeben.
Konfiguration: /etc/contracts-signature-api.json, root:www-data, Modus 0640.
Private Daten: /var/lib/contracts-signature-api, www-data, Modus 0700.
Bei Konfigurationsaenderungen Datei atomar ersetzen, Berechtigungen beibehalten.
SIGNATURE_API_CONFIG kann fuer isolierte Tests auf eine andere Konfiguration zeigen.
sudo php manage_api.php set-webhook base44 https://NEUE-URL/WEBHOOK
sudo php manage_api.php disable-client base44
sudo -u www-data php worker.php
Fuer Key-Rotation neuen Client anlegen und den alten nach Umstellung deaktivieren. Vorgaenge sind an ihre urspruengliche Client-ID gebunden; alte Vorgaenge benoetigen bis zum Abschluss weiterhin den alten Zugang.
Cron: /etc/cron.d/contracts-signature-api startet den Worker jede Minute.
Worker sind durch einen Prozesslock gegen parallele Verarbeitung geschuetzt.
Pro Lauf werden maximal 50 Auftraege bearbeitet; bei hoeherem Volumen Takt/Batch
und einen dedizierten Dienst bewusst anpassen. PHP-/Cron-Fehler werden im
Serverlog bzw. Cron-Fehlerkanal protokolliert; fehlgeschlagene Zustellungen zusaetzlich
in der Datenbank. Zustellfehler und freie Speicherkapazitaet betrieblich ueberwachen.
Bestehende SendGrid-Konfiguration wird verwendet; keine echten E-Mails fuer Tests
an fremde Empfaenger schicken.
SQLite verwendet WAL und Transaktionen; Backups muessen DB, WAL und private
Dokumentdateien konsistent erfassen, z.B. waehrend einer geplanten Schreibpause.
Aufbewahrung und Loeschfristen, AV-Vertrag, Zugriffsberechtigungen und Backups
muessen fuer die konkreten Vertragsdaten betrieblich festgelegt werden. Es gibt
absichtlich keine automatische Loeschung ohne festgelegte Aufbewahrungsregel.
IP und User-Agent werden nur im geschuetzten Protokoll gespeichert; ohne
explizit konfigurierte Proxy-Vertrauenskette wird REMOTE_ADDR verwendet.
Validierung
php tests/api_test.php
Der Test verwendet eine temporaere Konfiguration und private Testdatenbank,
einen kurzlebigen lokalen HTTP-Server, Mock-Transporte statt echter E-Mails/Webhooks
und raeumt seine Dateien und Prozesse auf.
Fuer unabhaengige visuelle Feldpruefung muss pdftoppm aus poppler-utils
installiert sein. Die Produktion wird durch diesen Test nicht beschrieben.
Vor Freigabe einer Base44-Anwendung zusaetzlich mit einem repraesentativen echten PDF, einem kontrollierten Postfach und dem echten Webhook testen: Anrede, Seiten/Feldpositionen, Mailannahme, Unterzeichnung, HMAC, Deduplizierung, Download/Hash und Zuordnung zum Base44-Vorgang.