Webhooks — Empfangen Sie Signaturedeignisse in Echtzeit
Konfigurieren Sie eine HTTPS-URL in Ihrem Certyneo-Dashboard und erhalten Sie einen mit HMAC-SHA256 signierten POST, sobald ein Ereignis in Ihren Umschlägen auftritt: Signatur, Ablehnung, Ablauf. 8 unterstützte Ereignisse, exponentielles Retry über 6 Versuche, kryptographische Verifizierung in 8 Codezeilen.
< 5s
Durchschnittliche Lieferfrist nach dem Ereignis
5x
Wiederholungsversuche bei Fehler (bis zu ~9h später)
HMAC-SHA256
Signaturalgorithmus für jede Anfrage
Ereigniskatalog
Die 8 unten stehenden Ereignisse decken den gesamten Lebenszyklus eines Certyneo-Umschlags ab. Aktivieren Sie diejenigen, die Sie interessieren, in Einstellungen → Webhooks, ignorieren Sie die anderen — das Abonnement ist granular pro Ereignis.
| Ereignis | Auslöser |
|---|---|
envelope.created | Eine Umschlag wird erstellt (über UI, API oder Vorlage) — nützlich, um einen CRM-Datensatz bei der Erstellung zu synchronisieren. |
envelope.sent | Der Umschlag wird an die Unterzeichner gesendet (erste E-Mail gesendet). Markiert den Beginn des aktiven Unterzeichnungszyklus. |
envelope.completed | Alle Unterzeichner haben unterzeichnet. Das versiegelte eIDAS-PDF und der Audit Trail sind über `proof_pdf_url` in der Payload verfügbar. |
envelope.declined | Ein Unterzeichner hat den Umschlag abgelehnt. Der Ablehnungsgrund (falls vom Unterzeichner angegeben) befindet sich in `data.decline_reason`. |
envelope.voided | Der Umschlag wurde vom Absender vor vollständiger Unterzeichnung storniert. Unterscheidbar von `expired` (manuell vs. Timeout). |
envelope.expired | Das Ablaufdatum des Umschlags ist verstrichen ohne vollständige Unterzeichnung. Die Liste der fehlenden Unterzeichner befindet sich in `data.missing_signers`. |
recipient.signed | Ein einzelner Unterzeichner hat unterzeichnet (aber nicht unbedingt alle). Nützlich für sequenzielle Workflows: Auslösung für den Übergang zum nächsten Unterzeichner. |
recipient.viewed | Ein Unterzeichner hat den Unterzeichnungslink geöffnet, ohne noch zu unterzeichnen. Nützlich für gezielte Nachverfolgungen. |
Payload-Format
Alle Ereignisse folgen dem gleichen JSON-Schema auf oberster Ebene: `event`, `envelope_id`, `occurred_at`, `data`. Der Inhalt von `data` variiert je nach Ereignis (Felder sind in der API-Referenz pro Ereignis dokumentiert). Hier ist ein vollständiges `envelope.completed`-Beispiel.
{
"event": "envelope.completed",
"envelope_id": "env_01HG3K8X9Y2N4P5Q6R7S8T9V0W",
"occurred_at": "2026-05-27T08:42:13.521Z",
"data": {
"title": "Contrat de prestation Acme Corp",
"status": "completed",
"created_at": "2026-05-24T14:12:00.000Z",
"completed_at": "2026-05-27T08:42:13.000Z",
"signers": [
{
"email": "client@acme.example",
"name": "Alex Client",
"signed_at": "2026-05-27T08:42:13.000Z",
"method": "eidas_aes",
"ip": "203.0.113.42"
}
],
"proof_pdf_url": "https://certyneo.com/api/envelopes/env_01HG.../proof.pdf"
}
}Die Payload ist UTF-8 codiert, ohne BOM. Die HMAC-Signatur wird auf dem rohen Body berechnet, wie er gesendet wird — verändern Sie keine Leerzeichen, das erneute JSON-Parsing ändert häufig die Reihenfolge der Schlüssel und bricht die Verifizierung.
HMAC-Signatur verifizieren
Jede Anfrage wird mit Ihrem Webhook-Secret signiert (sichtbar nur einmal bei der Erstellung im Dashboard, dann verschlüsselt im Ruhezustand). Die Signatur wird im Header `Certyneo-Signature` im Format `t=<timestamp>,v1=<hex_hmac>` übertragen. Verifizieren Sie IMMER die Signatur, bevor Sie die Payload verarbeiten — ohne diesen Schritt kann jeder ein Ereignis fälschen und Ihren Endpoint aufrufen.
Node.js / TypeScript
import crypto from "node:crypto";
export function verifyCertyneoSignature(
rawBody: string,
signatureHeader: string,
secret: string,
): boolean {
// Header format: t=<timestamp>,v1=<hex_hmac>
const parts = Object.fromEntries(
signatureHeader.split(",").map((p) => p.split("=")),
);
const ts = parts.t;
const sig = parts.v1;
if (!ts || !sig) return false;
// Reject events older than 5 minutes (replay protection).
const age = Math.abs(Date.now() / 1000 - Number(ts));
if (age > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.${rawBody}`)
.digest("hex");
// timingSafeEqual to mitigate timing attacks.
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(sig, "hex"),
);
}Python
import hashlib
import hmac
import time
def verify_certyneo_signature(
raw_body: bytes, signature_header: str, secret: str
) -> bool:
"""Verify a Certyneo webhook signature.
Header format: `t=<timestamp>,v1=<hex_hmac>`
Rejects events older than 5 minutes (replay protection).
"""
parts = dict(p.split("=") for p in signature_header.split(","))
ts = parts.get("t")
sig = parts.get("v1")
if not ts or not sig:
return False
if abs(time.time() - int(ts)) > 300:
return False
expected = hmac.new(
secret.encode("utf-8"),
f"{ts}.{raw_body.decode('utf-8')}".encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, sig)Häufiger Fehler
Verwenden Sie NICHT `===` oder `==` zum Vergleichen der erwarteten Signatur mit der empfangenen. Nutzen Sie eine Timing-sichere Funktion (`crypto.timingSafeEqual` in Node, `hmac.compare_digest` in Python). Ohne dies offenbart die Zeitdifferenz beim Vergleich zwischen zwei Signaturen einem geduldigen Angreifer schrittweise das Secret (Timing Attack).
Retry-Richtlinie
Falls Ihr Endpoint etwas anderes als HTTP 2xx antwortet (Timeout, 5xx, Verbindung verweigert), wiederholen wir mit exponenziellem Backoff. Insgesamt 6 Versuche über ~9 Stunden. Nach 6 Versuchen wird das Ereignis in Ihrem Webhook-Dashboard als `failed` markiert und eine Benachrichtigungs-E-Mail wird gesendet.
| Versuch | Kumulierte Frist | Verstrichene Zeit |
|---|---|---|
| #1 | 0 | 0 |
| #2 | + 1 min | 1 min |
| #3 | + 5 min | 6 min |
| #4 | + 30 min | 36 min |
| #5 | + 2 h | 2h 36 |
| #6 | + 6 h | 8h 36 |
Wenn Sie ein fehlgeschlagenes Ereignis manuell erneut senden möchten, bietet das Webhook-Dashboard eine Schaltfläche « Re-deliver » für jede fehlgeschlagene Zustellung — verfügbar für 7 Tage nach dem endgültigen Fehler.
Testen ohne echte Envelope zu versenden
Der Endpunkt `/v1/webhooks/test` akzeptiert eine URL und einen Ereignistyp und sendet ein fiktives Payload, das genau wie ein echtes signiert ist. Ideal zur Validierung Ihres Handlers in Continuous Integration oder während der lokalen Entwicklung (mit ngrok oder äquivalent).
# Trigger a test webhook to your endpoint
curl -X POST https://api.certyneo.com/v1/webhooks/test \
-H "Authorization: Bearer $CERTYNEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"event": "envelope.completed", "url": "https://your.app/webhooks/certyneo"}'6 Praktiken zu beachten
- HMAC-Signatur VOR dem Lesen des Body überprüfen — Timing-sichere Vergleiche verwenden.
- Jede `envelope_id` × `event` nur einmal verarbeiten, indem die bereits gesehenen IDs in der Datenbank gespeichert werden (Wiederholungen können dasselbe Ereignis erneut liefern).
- HTTP 200 innerhalb von maximal 5 Sekunden antworten, dann asynchron verarbeiten (Queue). Andernfalls erhalten Sie einen Timeout und werden als Wiederholung gezählt.
- Body brut + vollständige Signatur im Debug-Modus protokollieren — die HMAC-Verifizierung schlägt oft bei BOM oder unsichtbarem Whitespace fehl.
- Dead-Letter-Queue für `failed`-Ereignisse verbinden, um manuell erneut zu liefern, falls auf Ihrer Seite ein Incident auftritt.
- Eine Zeitabweichung von 5 Minuten zwischen `t=` und Empfangszeit tolerieren — darüber hinaus ablehnen, um Replays zu blockieren.
Weiterführende Informationen
Bereit, Ihre Systeme zu verbinden?
Erstellen Sie ein kostenloses Konto in 2 Minuten — die Webhook-Konfiguration ist bereits im Starter-Plan verfügbar (kostenlos, 5 Envelopes/Monat).