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

  1. Base44 erzeugt das PDF und kennt die exakten Signaturpositionen.
  2. Base44 laedt PDF und JSON-Metadaten mit einem eindeutigen Idempotency-Key hoch.
  3. Die API persistiert Original, Vorschau, Unterzeichner und Versandauftraege.
  4. Der Worker verschickt jedem Unterzeichner einen persoenlichen Link. Die API-Antwort auf den Upload bedeutet noch nicht, dass eine E-Mail verschickt wurde.
  5. 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.
  6. Nach allen Unterschriften erzeugt der Worker das finale PDF und das PDF-Protokoll.
  7. Erst nach erfolgreicher Speicherung wird request.completed mit Downloadlinks verschickt. Base44 laedt das finale PDF herunter und ordnet es ueber external_id zu.

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

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.