Aller au contenu

Intégration · Référence

Webhooks

Envoyez des événements signés à vos systèmes externes.

Utilisez les webhooks pour recevoir les événements des apprenants et des notifications au lieu d'interroger l'API.

Créer un webhook

  1. Ouvrez Administration > Webhooks et créez un webhook.
  2. Saisissez l'URL d'un endpoint public.
  3. Sélectionnez les événements ou laissez la liste vide pour tous les recevoir.
  4. Ajoutez les en-têtes personnalisés, activez le webhook et enregistrez-le.
  5. Copiez le secret de signature dans votre gestionnaire de secrets.

Chaque organisation peut avoir jusqu'à cinq webhooks. La désactivation suspend les nouvelles livraisons sans supprimer le webhook.

Événements

ÉvénementMoment de l'envoi
learner.startedUn apprenant commence une activité.
learner.updatedLa tentative enregistrée d'un apprenant change.
learner.completedUne tentative devient terminée, réussie ou échouée.
course.inviteUn apprenant est invité à un cours.
course.requestUn apprenant demande l'accès à un cours.
course.request.acceptedUne demande d'accès à un cours est acceptée.
course.request.rejectedUne demande d'accès à un cours est refusée.
course.completionUne notification d'achèvement de cours est créée.
course.inactiveUne notification de cours inactif est créée.
collection.inviteUn apprenant est invité à une collection.
collection.requestUn apprenant demande l'accès à une collection.
collection.request.acceptedUne demande d'accès à une collection est acceptée.
collection.request.rejectedUne demande d'accès à une collection est refusée.
collection.completionUne notification d'achèvement de collection est créée.

Kokobi envoie une requête HTTP POST contenant { "event": string, "data": object }. Les dates sont des chaînes ISO 8601. Une sélection vide reçoit aussi les nouveaux types d'événements. Ignorez les champs inutilisés par votre gestionnaire.

En-têtes et signatures

Chaque livraison comprend :

plaintext
Content-Type: application/json
webhook-timestamp: 2026-07-24T12:00:00.000Z
webhook-signature: <signature-hexadécimale>

Les en-têtes personnalisés sont aussi inclus. Content-Type, webhook-timestamp et webhook-signature sont réservés.

La signature est le condensat HMAC-SHA256 encodé en hexadécimal de :

plaintext
timestamp + "." + raw_request_body

Vérifiez-la avec les octets exacts du corps avant d'analyser le JSON :

ts
import { createHmac, timingSafeEqual } from "node:crypto";

const verify = (body: Buffer, timestamp: string, signature: string) => {
  const expected = createHmac("sha256", process.env.KOKOBI_WEBHOOK_SECRET!)
    .update(timestamp + ".")
    .update(body)
    .digest();
  const provided = Buffer.from(signature, "hex");

  return (
    provided.length === expected.length && timingSafeEqual(provided, expected)
  );
};

Refusez aussi les anciens horodatages pour limiter les attaques par rejeu. N'exposez jamais le secret dans du code côté client.

Livraison

Une réponse 2xx marque la livraison comme réussie. Les erreurs réseau et les autres réponses font l'objet de nouvelles tentatives avec des délais croissants.

Vérifiez la signature, enregistrez ou mettez l'événement en file d'attente, puis répondez rapidement. Traitez les tâches lentes de façon asynchrone et dédupliquez les événements, car une nouvelle tentative peut livrer les mêmes données plusieurs fois. Les livraisons terminées deviennent admissibles au nettoyage après sept jours.

Dépannage

  • Livraison en attente : confirmez que l'URL est publique, accepte les requêtes POST JSON et retourne 2xx.
  • Signature invalide : utilisez le bon secret et le corps brut non modifié. N'ajoutez pas de saut de ligne.
  • Événement absent : confirmez que le webhook est activé, que son filtre correspond et que l'action a eu lieu dans la même organisation.

Consultez le journal de livraison de Kokobi et les journaux de votre pare-feu, proxy et application avant de réessayer.

Types de données des événements

Les types suivants décrivent la propriété data de chaque événement. Les dates sont des chaînes ISO 8601. Des champs peuvent être ajoutés; ignorez les propriétés inutilisées par votre gestionnaire.

Événements apprenant

learner.started, learner.updated et learner.completed utilisent la même structure.

ts
export type LearnerStartedData = LearnerEventData;
export type LearnerUpdatedData = LearnerEventData;
export type LearnerCompletedData = LearnerEventData;

export type LearnerEventData = {
  user: {
    id: string;
    email: string;
    name: string;
  };
  connection: {
    userId: string;
    organizationId: string;
    externalId: string | null;
    respondedAt: string | null;
    connectType: "invite" | "request";
    connectStatus:
      | "pending"
      | "accepted"
      | "rejected"
      | "disconnected"
      | "removed";
    createdAt: string;
    updatedAt: string;
  } & ({ courseId: string } | { collectionId: string });
  attempt: {
    id: string;
    userId: string;
    organizationId: string;
    moduleId: string;
    courseId: string;
    completedAt: string | null;
    data: Record<string, string>;
    status: "completed" | "passed" | "failed" | "in-progress" | "not-started";
    score?: { raw?: number; min?: number; max?: number };
    module: {
      id: string;
      name: string | null;
      courseId: string;
      locale: "en" | "fr";
      multilingual: boolean;
      type: "1.2" | "2004";
      versionNumber: number;
      sizeBytes: number | null;
      deletedAt: string | null;
      createdAt: string;
      updatedAt: string;
    };
    createdAt: string;
    updatedAt: string;
  } | null;
};

Événements de cours

ts
export type CourseInviteData = {
  to: string[];
  data: { id: string; reminderDays?: 14 | 30 | 90 };
};

export type CourseInactiveData = {
  to: string[];
  data: { id: string; reminderDays?: 30 | 90 };
};

export type CourseCompletionData = {
  to: string[];
  data: { id: string };
};

export type CourseRequestData = {
  to: string[];
  data: {
    id: string;
    userId: string;
    userEmail: string;
    userName?: string;
  };
};

export type CourseRequestAcceptedData = {
  to: string[];
  data: { id: string };
};

export type CourseRequestRejectedData = {
  to: string[];
  data: { id: string };
};

Événements de collection

ts
export type CollectionInviteData = {
  to: string[];
  data: { id: string; reminderDays?: 14 | 30 | 90 };
};

export type CollectionCompletionData = {
  to: string[];
  data: { id: string };
};

export type CollectionRequestData = {
  to: string[];
  data: {
    id: string;
    userId: string;
    userEmail: string;
    userName?: string;
  };
};

export type CollectionRequestAcceptedData = {
  to: string[];
  data: { id: string };
};

export type CollectionRequestRejectedData = {
  to: string[];
  data: { id: string };
};