Référence des événements webhook
Schémas de charge utile détaillés et exemples JSON pour chaque type d'événement webhook ZenFlip, incluant les en-têtes de requête et un exemple de vérification de signature en Node.js.
- Enveloppe commune
- En-têtes de requête
- publication.created
- Schéma de la charge utile
- Exemple de charge utile
- publication.converted
- Schéma de la charge utile
- Exemple de charge utile (succès)
- Exemple de charge utile (échec)
- publication.deleted
- Schéma de la charge utile
- Exemple de charge utile
- lead.captured
- Schéma de la charge utile
- Exemple de charge utile
- team.member_joined
- Schéma de la charge utile
- Exemple de charge utile
- export.completed
- Schéma de la charge utile
- Exemple de charge utile
- Vérification de signature (exemple complet)
- Bonnes pratiques
Référence des événements webhook
Ce document fournit le schéma complet de la charge utile et des exemples JSON pour chaque type d'événement webhook pris en charge par ZenFlip. Toutes les charges utiles suivent la même structure de niveau supérieur.
Enveloppe commune
Chaque charge utile webhook partage cette structure :
`json { "id": "wh_del_{uuid}", "event": "{event_type}", "createdAt": "ISO 8601 timestamp", "organizationId": "{org_uuid}", "data": { } } `
En-têtes de requête
Chaque requête HTTP POST de webhook inclut ces en-têtes :
En-tête | Valeur d'exemple |
|---|---|
|
|
|
|
|
|
|
|
|
|
publication.created
Déclenché lorsqu'une nouvelle publication est créée dans l'organisation.
Schéma de la charge utile
Champ | Type | Description |
|---|---|---|
| string | UUID de la nouvelle publication |
| string | Titre de la publication |
| string | Slug adapté aux URL |
| string | Statut initial (toujours |
| string | UUID de l'utilisateur qui l'a créée |
Exemple de charge utile
`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
Déclenché lorsqu'une tâche de conversion PDF se termine, avec succès ou en erreur.
Schéma de la charge utile
Champ | Type | Description |
|---|---|---|
| string | UUID de la publication |
| string | Titre de la publication |
| string | UUID de la version convertie |
| integer | Numéro de version séquentiel |
| string | Résultat de la conversion : |
| integer | Nombre de pages générées (0 en cas d'échec) |
| string | Message d'erreur (null en cas de succès) |
| string | Horodatage ISO 8601 de la fin de conversion |
Exemple de charge utile (succès)
`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" } } `
Exemple de charge utile (échec)
`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
Déclenché lorsqu'une publication est supprimée définitivement.
Schéma de la charge utile
Champ | Type | Description |
|---|---|---|
| string | UUID de la publication supprimée |
| string | Titre au moment de la suppression |
| string | Slug au moment de la suppression |
| string | UUID de l'utilisateur qui l'a supprimée |
Exemple de charge utile
`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
Déclenché lorsqu'un lecteur soumet un formulaire de capture de prospect dans une publication.
Schéma de la charge utile
Champ | Type | Description |
|---|---|---|
| string | UUID du prospect capturé |
| string | UUID de la publication |
| string | Adresse email soumise |
| string | Nom complet (null si non collecté) |
| string | Nom de l'entreprise (null si non collecté) |
| string | Numéro de téléphone (null si non collecté) |
| object | Paires clé-valeur des champs personnalisés |
| integer | Page où le formulaire a été déclenché |
| string | Méthode de déclenchement : |
| string | Horodatage ISO 8601 de la soumission |
Exemple de charge utile
`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
Déclenché lorsqu'un membre de l'équipe accepte son invitation et rejoint l'organisation.
Schéma de la charge utile
Champ | Type | Description |
|---|---|---|
| string | UUID du nouveau membre de l'équipe |
| string | Adresse email du membre |
| string | Nom d'affichage |
| string | Rôle attribué : |
| string | UUID de l'utilisateur qui a envoyé l'invitation |
| string | Horodatage ISO 8601 de l'adhésion |
Exemple de charge utile
`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
Déclenché lorsqu'une tâche d'export HTML ou SCORM termine son traitement.
Schéma de la charge utile
Champ | Type | Description |
|---|---|---|
| string | UUID de la tâche d'export |
| string | UUID de la publication exportée |
| string | Format d'export : |
| string | Résultat : |
| string | URL pré-signée pour télécharger l'export (null en cas d'échec, expire dans 24 heures) |
| integer | Taille du fichier d'export en octets (0 en cas d'échec) |
| string | Message d'erreur (null en cas de succès) |
| string | Horodatage ISO 8601 de la fin |
Exemple de charge utile
`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" } } `
Vérification de signature (exemple complet)
Voici un serveur Node.js Express complet qui reçoit et vérifie les webhooks ZenFlip :
`javascript const express = require("express"); const crypto = require("crypto");
const app = express(); const WEBHOOK_SECRET = process.env.ZENFLIP_WEBHOOK_SECRET;
// IMPORTANT : Utilisez express.raw() pour obtenir le corps brut pour la vérification de signature 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"];
// Étape 1 : Vérifier la signature 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" }); }
// Étape 2 : Analyser la charge utile const event = JSON.parse(req.body.toString());
// Étape 3 : Accuser réception immédiatement res.status(200).json({ received: true });
// Étape 4 : Traiter l'événement de manière asynchrone 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); // Transférer vers le CRM, envoyer une notification, etc. 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"); }); `
Bonnes pratiques
Vérifiez toujours les signatures. Ne faites jamais confiance à une charge utile webhook sans valider l'en-tête
X-ZenFlip-Signature.Répondez rapidement. Renvoyez un statut
200immédiatement et traitez l'événement de manière asynchrone. Les livraisons de webhooks expirent après 10 secondes.Dédupliquez par identifiant de livraison. Stockez les valeurs
idtraitées et ignorez les doublons. Les tentatives de reprise peuvent entraîner la livraison du même événement plus d'une fois.Utilisez des endpoints HTTPS. ZenFlip n'envoie des webhooks qu'aux URL utilisant HTTPS.
Gérez les échecs avec élégance. Si un service en aval est indisponible, mettez l'événement en file d'attente pour un traitement ultérieur plutôt que de renvoyer un statut d'erreur.