Webhook-Ereignis-Referenz
Detaillierte Payload-Schemas und Beispiel-JSON für jeden ZenFlip-Webhook-Ereignistyp, einschließlich Anfrage-Header und eines Node.js-Signaturverifizierungsbeispiels.
- Gemeinsame Hüllstruktur
- Anfrage-Header
- publication.created
- Payload-Schema
- Beispiel-Payload
- publication.converted
- Payload-Schema
- Beispiel-Payload (Erfolg)
- Beispiel-Payload (Fehler)
- publication.deleted
- Payload-Schema
- Beispiel-Payload
- lead.captured
- Payload-Schema
- Beispiel-Payload
- team.member_joined
- Payload-Schema
- Beispiel-Payload
- export.completed
- Payload-Schema
- Beispiel-Payload
- Signaturverifizierung (Vollständiges Beispiel)
- Best Practices
Webhook-Ereignis-Referenz
Dieses Dokument enthält das vollständige Payload-Schema und Beispiel-JSON für jeden von ZenFlip unterstützten Webhook-Ereignistyp. Alle Payloads folgen derselben übergeordneten Struktur.
Gemeinsame Hüllstruktur
Jeder Webhook-Payload teilt diese Struktur:
`json { "id": "wh_del_{uuid}", "event": "{event_type}", "createdAt": "ISO 8601 timestamp", "organizationId": "{org_uuid}", "data": { } } `
Anfrage-Header
Jede Webhook-HTTP-POST-Anfrage enthält diese Header:
Header | Beispielwert |
|---|---|
|
|
|
|
|
|
|
|
|
|
publication.created
Wird ausgelöst, wenn eine neue Publikation in der Organisation erstellt wird.
Payload-Schema
Feld | Typ | Beschreibung |
|---|---|---|
| string | UUID der neuen Publikation |
| string | Publikationstitel |
| string | URL-freundlicher Slug |
| string | Initialstatus (immer |
| string | UUID des Benutzers, der sie erstellt hat |
Beispiel-Payload
`json { "id": "wh_del_a1b2c3d4-0001-0001-0001-000000000001", "event": "publication.created", "createdAt": "2026-02-20T12:00:00.000Z", "organizationId": "660e8400-e29b-41d4-a716-446655440000", "data": { "publicationId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "title": "Q1 Sales Report", "slug": "q1-sales-report", "status": "draft", "createdBy": "550e8400-e29b-41d4-a716-446655440000" } } `
publication.converted
Wird ausgelöst, wenn ein PDF-Konvertierungsauftrag abgeschlossen wird, unabhängig davon, ob erfolgreich oder mit einem Fehler.
Payload-Schema
Feld | Typ | Beschreibung |
|---|---|---|
| string | UUID der Publikation |
| string | Publikationstitel |
| string | UUID der konvertierten Version |
| integer | Fortlaufende Versionsnummer |
| string | Konvertierungsergebnis: |
| integer | Anzahl der generierten Seiten (0 bei Fehler) |
| string | Fehlermeldung (null bei Erfolg) |
| string | ISO 8601-Zeitstempel des Konvertierungsabschlusses |
Beispiel-Payload (Erfolg)
`json { "id": "wh_del_a1b2c3d4-0002-0002-0002-000000000002", "event": "publication.converted", "createdAt": "2026-02-20T14:05:00.000Z", "organizationId": "660e8400-e29b-41d4-a716-446655440000", "data": { "publicationId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "title": "Q1 Sales Report", "versionId": "c3d4e5f6-a7b8-9012-cdef-123456789012", "versionNumber": 1, "status": "ready", "pageCount": 24, "conversionError": null, "convertedAt": "2026-02-20T14:05:00.000Z" } } `
Beispiel-Payload (Fehler)
`json { "id": "wh_del_a1b2c3d4-0002-0002-0002-000000000003", "event": "publication.converted", "createdAt": "2026-02-20T14:06:00.000Z", "organizationId": "660e8400-e29b-41d4-a716-446655440000", "data": { "publicationId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "title": "Q1 Sales Report", "versionId": "d4e5f6a7-b8c9-0123-def0-234567890123", "versionNumber": 2, "status": "failed", "pageCount": 0, "conversionError": "PDF file is password-protected and cannot be processed", "convertedAt": "2026-02-20T14:06:00.000Z" } } `
publication.deleted
Wird ausgelöst, wenn eine Publikation dauerhaft gelöscht wird.
Payload-Schema
Feld | Typ | Beschreibung |
|---|---|---|
| string | UUID der gelöschten Publikation |
| string | Titel zum Zeitpunkt der Löschung |
| string | Slug zum Zeitpunkt der Löschung |
| string | UUID des Benutzers, der gelöscht hat |
Beispiel-Payload
`json { "id": "wh_del_a1b2c3d4-0003-0003-0003-000000000004", "event": "publication.deleted", "createdAt": "2026-02-20T16:00:00.000Z", "organizationId": "660e8400-e29b-41d4-a716-446655440000", "data": { "publicationId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "title": "Q1 Sales Report", "slug": "q1-sales-report", "deletedBy": "550e8400-e29b-41d4-a716-446655440000" } } `
lead.captured
Wird ausgelöst, wenn ein Betrachter ein Lead-Capture-Formular in einer Publikation einreicht.
Payload-Schema
Feld | Typ | Beschreibung |
|---|---|---|
| string | UUID des erfassten Leads |
| string | UUID der Publikation |
| string | Eingereichte E-Mail-Adresse |
| string | Vollständiger Name (null wenn nicht erfasst) |
| string | Firmenname (null wenn nicht erfasst) |
| string | Telefonnummer (null wenn nicht erfasst) |
| object | Schlüssel-Wert-Paare aus benutzerdefinierten Formularfeldern |
| integer | Seite, auf der das Formular ausgelöst wurde |
| string | Auslöser-Methode: |
| string | ISO 8601-Zeitstempel der Einreichung |
Beispiel-Payload
`json { "id": "wh_del_a1b2c3d4-0004-0004-0004-000000000005", "event": "lead.captured", "createdAt": "2026-02-20T15:30:00.000Z", "organizationId": "660e8400-e29b-41d4-a716-446655440000", "data": { "leadId": "d4e5f6a7-b8c9-0123-def0-234567890123", "publicationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "email": "jane@example.com", "name": "Jane Doe", "company": "Acme Inc.", "phone": "+1-555-0100", "customFields": { "jobTitle": "Product Manager", "department": "Marketing" }, "sourcePage": 3, "triggerType": "page", "capturedAt": "2026-02-20T15:30:00.000Z" } } `
team.member_joined
Wird ausgelöst, wenn ein Teammitglied seine Einladung annimmt und der Organisation beitritt.
Payload-Schema
Feld | Typ | Beschreibung |
|---|---|---|
| string | UUID des neuen Teammitglieds |
| string | E-Mail-Adresse des Mitglieds |
| string | Anzeigename |
| string | Zugewiesene Rolle: |
| string | UUID des Benutzers, der die Einladung gesendet hat |
| string | ISO 8601-Zeitstempel des Beitritts |
Beispiel-Payload
`json { "id": "wh_del_a1b2c3d4-0005-0005-0005-000000000006", "event": "team.member_joined", "createdAt": "2026-02-20T17:00:00.000Z", "organizationId": "660e8400-e29b-41d4-a716-446655440000", "data": { "userId": "880e8400-e29b-41d4-a716-446655440003", "email": "neweditor@example.com", "name": "Jordan Lee", "role": "editor", "invitedBy": "550e8400-e29b-41d4-a716-446655440000", "joinedAt": "2026-02-20T17:00:00.000Z" } } `
export.completed
Wird ausgelöst, wenn ein HTML- oder SCORM-Exportauftrag die Verarbeitung abschließt.
Payload-Schema
Feld | Typ | Beschreibung |
|---|---|---|
| string | UUID des Exportauftrags |
| string | UUID der exportierten Publikation |
| string | Exportformat: |
| string | Ergebnis: |
| string | Vorsignierte URL zum Herunterladen des Exports (null bei Fehler, läuft nach 24 Stunden ab) |
| integer | Größe der Exportdatei in Bytes (0 bei Fehler) |
| string | Fehlermeldung (null bei Erfolg) |
| string | ISO 8601-Zeitstempel des Abschlusses |
Beispiel-Payload
`json { "id": "wh_del_a1b2c3d4-0006-0006-0006-000000000007", "event": "export.completed", "createdAt": "2026-02-20T18:00:00.000Z", "organizationId": "660e8400-e29b-41d4-a716-446655440000", "data": { "exportJobId": "f6a7b8c9-d0e1-2345-f012-456789012345", "publicationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "format": "scorm_12", "status": "completed", "downloadUrl": "https://cdn.zenflip.io/exports/f6a7b8c9...?token=abc123&expires=1740182400", "fileSizeBytes": 15728640, "error": null, "completedAt": "2026-02-20T18:00:00.000Z" } } `
Signaturverifizierung (Vollständiges Beispiel)
Hier ist ein vollständiger Node.js Express-Server, der ZenFlip-Webhooks empfängt und verifiziert:
`javascript const express = require("express"); const crypto = require("crypto");
const app = express(); const WEBHOOK_SECRET = process.env.ZENFLIP_WEBHOOK_SECRET;
// WICHTIG: express.raw() verwenden, um den Roh-Body für die Signaturverifizierung zu erhalten app.post( "/webhooks/zenflip", express.raw({ type: "application/json" }), (req, res) => { const signature = req.headers["x-zenflip-signature"]; const eventType = req.headers["x-zenflip-event"]; const deliveryId = req.headers["x-zenflip-delivery-id"];
// Schritt 1: Signatur verifizieren const expected = crypto .createHmac("sha256", WEBHOOK_SECRET) .update(req.body, "utf8") .digest("hex");
const isValid = crypto.timingSafeEqual( Buffer.from(signature, "hex"), Buffer.from(expected, "hex") );
if (!isValid) { console.error("Webhook signature verification failed:", deliveryId); return res.status(401).json({ error: "Invalid signature" }); }
// Schritt 2: Payload parsen const event = JSON.parse(req.body.toString());
// Schritt 3: Sofort bestätigen res.status(200).json({ received: true });
// Schritt 4: Ereignis asynchron verarbeiten handleEvent(event).catch((err) => { console.error("Error processing webhook:", err); }); } );
async function handleEvent(event) { switch (event.event) { case "publication.created": console.log("New publication:", event.data.title); break;
case "publication.converted": if (event.data.status === "ready") { console.log( "Conversion complete:", event.data.title, "-", event.data.pageCount, "pages" ); } else { console.error("Conversion failed:", event.data.conversionError); } break;
case "lead.captured": console.log("New lead:", event.data.email, "from page", event.data.sourcePage); // An CRM weiterleiten, Benachrichtigung senden, usw. break;
case "team.member_joined": console.log("New team member:", event.data.name, "as", event.data.role); break;
case "export.completed": if (event.data.status === "completed") { console.log("Export ready:", event.data.downloadUrl); } break;
default: console.log("Unhandled event type:", event.event); } }
app.listen(3000, () => { console.log("Webhook server listening on port 3000"); }); `
Best Practices
Signaturen immer verifizieren. Vertrauen Sie niemals einem Webhook-Payload, ohne den
X-ZenFlip-Signature-Header zu validieren.Schnell antworten. Geben Sie sofort einen
200-Status zurück und verarbeiten Sie das Ereignis asynchron. Webhook-Zustellungen brechen nach 10 Sekunden ab.Nach Zustellungs-ID deduplizieren. Speichern Sie verarbeitete
id-Werte und überspringen Sie Duplikate. Wiederholungsversuche können dazu führen, dass dasselbe Ereignis mehr als einmal zugestellt wird.HTTPS-Endpunkte verwenden. ZenFlip sendet Webhooks nur an URLs mit HTTPS.
Fehler elegant behandeln. Wenn ein nachgelagerter Dienst nicht verfügbar ist, stellen Sie das Ereignis für spätere Verarbeitung in eine Warteschlange, anstatt einen Fehlerstatus zurückzugeben.