Zum Inhalt springen

Webhooks

Statt zu pollen, kannst du dir von Accessful ein signiertes Event an deinen Server POSTen lassen, sobald ein Job seinen Zustand ändert.

Es gibt kein Dashboard und keinen Registrierungs-Endpunkt. Du hängst einen Callback pro Upload an, und du wählst das Signing-Secret selbst — Accessful stellt keins aus.

  • Multipart-Upload — sende die Felder webhookUrl und secret.
  • Upload per URL — sende die JSON-Felder callbackUrl und hmacSignature.
Terminal-Fenster
curl -X POST "https://api.accessful.de/api/v1/upload-service/pdf/upload" \
-H "X-API-Key: $ACCESSFUL_API_KEY" \
-F "files=@dokument.pdf" \
-F "webhookUrl=https://deine-app.example.com/hooks/accessful" \
-F "secret=$DEIN_WEBHOOK_SECRET"
Event typeFeuert, wenn
case.runningDie Verarbeitung gestartet ist. Kann mehrfach feuern (einmal pro Iteration).
case.completedDas PDF/UA-Ergebnis zum Download bereit ist.
case.failedDie Verarbeitung fehlgeschlagen ist.
case.canceledDer Case abgebrochen wurde.
case.quota_exceededAbgelehnt, weil das Vertragskontingent erschöpft ist.

Die Zustände queued und quota_pending werden nicht als Webhook ausgeliefert.

Der Request-Body ist JSON. Content-Type: application/json.

{
"id": "f1d2c3b4-0000-4a1e-8f3c-2d6b5a9e1c40",
"type": "case.completed",
"apiVersion": "2026-06-05",
"occurredAt": "2026-06-05T12:34:56Z",
"data": {
"caseId": "7c2f1e4a-9b0d-4a1e-8f3c-2d6b5a9e1c40",
"fileName": "dokument.pdf",
"jobStatus": "completed"
}
}
FeldTypHinweise
idUUIDEvent-ID. Nutze sie als Idempotenz-Schlüssel (siehe unten).
typestringEiner der obigen Event-Typen.
apiVersionstringVertragsversion (2026-06-05). Pinne sie, um Änderungen zu erkennen.
occurredAtISO-8601Erstellzeitpunkt des Events; stabil über Retries.
data.caseIdUUIDDer Case, um den es geht.
data.fileNamestringDer Dateiname.
data.jobStatusstringRoher Job-Status, z. B. completed, failed, canceled, quota_exceeded.

Null-Felder werden im JSON weggelassen.

HeaderBeispielZweck
X-Accessful-Signaturet=1749126896,v1=9f86d0…HMAC-Signatur — diese verifizieren
X-Accessful-Webhook-Timestamp1749126896Unix-Sekunden; identisch mit t= oben
X-Accessful-Event-Idf1d2c3b4-…Entspricht id; Idempotenz-Schlüssel
X-Accessful-Event-Typecase.completedRouting ohne Body-Parsing
X-Accessful-Case-Id7c2f1e4a-…Die Case-ID
X-Accessful-Delivery-Attempt1Versuchszähler, 1-basiert
X-Signaturen4bQgY…legacy base64-HMAC nur über den Body

Der X-Accessful-Signature-Header hat die Form t=<unix>,v1=<hex>. Berechne sie neu und vergleiche in konstanter Zeit:

  1. Lies t und v1 aus dem Header.
  2. Berechne HMAC-SHA256(secret, "<t>.<roher Body>") und hex-kodiere es. Die signierte Zeichenkette ist der Timestamp, ein wörtlicher Punkt, dann der exakte rohe Request-Body — verifiziere vor dem JSON-Parsen.
  3. Vergleiche in konstanter Zeit gegen v1.
  4. Optional: lehne ab, wenn t älter als ein paar Minuten ist (Replay-Schutz).
import crypto from 'node:crypto';
// `rawBody` müssen die exakt empfangenen Bytes sein (z. B. express.raw()).
function verify(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.split('=')));
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // 5 Min
return valid && fresh;
}
  • Erfolg = jede 2xx-Antwort innerhalb des Timeouts (5s zum Verbinden, 10s zum Antworten). Antworte schnell und erledige deine Arbeit asynchron.
  • Fehler (kein 2xx, Timeout oder Verbindungsfehler) wird bis zu 10 Mal mit exponentiellem Backoff und Jitter wiederholt — etwa 10s → 30s → 1,5m → 4,5m → 13,5m → 40m → 2h, dann gedeckelt bei 6h, abzüglich bis zu 20% Jitter. Das Gesamtfenster erstreckt sich über mehrere Stunden.
  • Nach 10 fehlgeschlagenen Versuchen wird das Event aufgegeben. Für eine erneute Zustellung wende dich an den Support.
  • Idempotenz: Dasselbe Event kann mehr als einmal ankommen (z. B. ein Retry, nachdem dein Endpunkt bereits erfolgreich war). Dedupliziere über X-Accessful-Event-Id — sie ist über jeden Retry stabil.