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.

On this page

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

Content-Type

application/json

User-Agent

ZenFlip-Webhooks/1.0

X-ZenFlip-Event

publication.created

X-ZenFlip-Delivery-Id

wh_del_550e8400-e29b-41d4-a716-446655440000

X-ZenFlip-Signature

a3f2b8c91d... (condensé hexadécimal HMAC-SHA256)


publication.created

Déclenché lorsqu'une nouvelle publication est créée dans l'organisation.

Schéma de la charge utile

Champ

Type

Description

data.publicationId

string

UUID de la nouvelle publication

data.title

string

Titre de la publication

data.slug

string

Slug adapté aux URL

data.status

string

Statut initial (toujours draft)

data.createdBy

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

data.publicationId

string

UUID de la publication

data.title

string

Titre de la publication

data.versionId

string

UUID de la version convertie

data.versionNumber

integer

Numéro de version séquentiel

data.status

string

Résultat de la conversion : ready ou failed

data.pageCount

integer

Nombre de pages générées (0 en cas d'échec)

data.conversionError

string

Message d'erreur (null en cas de succès)

data.convertedAt

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

data.publicationId

string

UUID de la publication supprimée

data.title

string

Titre au moment de la suppression

data.slug

string

Slug au moment de la suppression

data.deletedBy

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

data.leadId

string

UUID du prospect capturé

data.publicationId

string

UUID de la publication

data.email

string

Adresse email soumise

data.name

string

Nom complet (null si non collecté)

data.company

string

Nom de l'entreprise (null si non collecté)

data.phone

string

Numéro de téléphone (null si non collecté)

data.customFields

object

Paires clé-valeur des champs personnalisés

data.sourcePage

integer

Page où le formulaire a été déclenché

data.triggerType

string

Méthode de déclenchement : page, exit, timer, scroll

data.capturedAt

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

data.userId

string

UUID du nouveau membre de l'équipe

data.email

string

Adresse email du membre

data.name

string

Nom d'affichage

data.role

string

Rôle attribué : admin, editor, viewer

data.invitedBy

string

UUID de l'utilisateur qui a envoyé l'invitation

data.joinedAt

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

data.exportJobId

string

UUID de la tâche d'export

data.publicationId

string

UUID de la publication exportée

data.format

string

Format d'export : html, scorm_12, scorm_2004

data.status

string

Résultat : completed ou failed

data.downloadUrl

string

URL pré-signée pour télécharger l'export (null en cas d'échec, expire dans 24 heures)

data.fileSizeBytes

integer

Taille du fichier d'export en octets (0 en cas d'échec)

data.error

string

Message d'erreur (null en cas de succès)

data.completedAt

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

  1. Vérifiez toujours les signatures. Ne faites jamais confiance à une charge utile webhook sans valider l'en-tête X-ZenFlip-Signature.

  2. Répondez rapidement. Renvoyez un statut 200 immédiatement et traitez l'événement de manière asynchrone. Les livraisons de webhooks expirent après 10 secondes.

  3. Dédupliquez par identifiant de livraison. Stockez les valeurs id traitées et ignorez les doublons. Les tentatives de reprise peuvent entraîner la livraison du même événement plus d'une fois.

  4. Utilisez des endpoints HTTPS. ZenFlip n'envoie des webhooks qu'aux URL utilisant HTTPS.

  5. 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.

Next →
Vue d'ensemble des webhooks