createWebhookService

expérimentaldepuis 0.1.0@legba-core/realtime

createWebhookService

Ce que ça fait

Vérifie la signature des webhooks envoyés par un serveur LiveKit (JWT Authorization + sha256 du corps) et applique l’idempotence des événements rejoués.

Signature

function createWebhookService(config: WebhookServiceConfig): WebhookService

Paramètres

NomTypeRequisDéfautDescription
apiKeystringouiClé API LiveKit (celle qui a signé le webhook)
apiSecretstringouiSecret API LiveKit

Retour

Un WebhookService :

MéthodeRôle
verifyWebhook(body, authHeader)Vérifie la signature et rend l’événement désérialisé (LegbaWebhookEvent). Lève LegbaWebhookError avant tout traitement du contenu si invalide.
processWebhook(body, authHeader, store)verifyWebhook puis applique l’idempotence via store : rend { event, isDuplicate }.
rawInstance réelle WebhookReceiver du SDK — échappatoire typée, voir docs/recipes/raw-escape-hatch.md

Erreurs

CodeQuandComment corriger
LEGBA_WEBHOOK_INVALID_SIGNATUREEn-tête Authorization absent, JWT invalide/expiré, ou sha256 du corps ne correspondant pasVérifier que apiKey/apiSecret correspondent à ceux configurés côté LiveKit, et que le corps brut (non reparsé) est passé tel quel

Exemple minimal

import { createWebhookService, createInMemoryWebhookIdempotencyStore } from "@legba-core/realtime";

const webhooks = createWebhookService({ apiKey, apiSecret });
const store = createInMemoryWebhookIdempotencyStore();

const { event, isDuplicate } = await webhooks.processWebhook(rawBody, authHeader, store);
if (!isDuplicate) {
  console.log(`${event.event} pour ${event.roomName}`);
}

Exemple complet

import {
  createWebhookService,
  createInMemoryWebhookIdempotencyStore,
  LegbaWebhookError,
} from "@legba-core/realtime";

const webhooks = createWebhookService({ apiKey, apiSecret });
const store = createInMemoryWebhookIdempotencyStore();

// "app" est un framework HTTP au choix de l'appelant (Express, Fastify, ...) ;
// req/res sont ici typés en any car leur forme dépend de ce choix.
app.post("/webhooks/livekit", async (req: any, res: any) => {
  try {
    const { event, isDuplicate } = await webhooks.processWebhook(
      req.rawBody,
      req.headers.authorization,
      store,
    );
    if (!isDuplicate) {
      events.push(event);
    }
    res.sendStatus(200);
  } catch (error) {
    if (error instanceof LegbaWebhookError) {
      res.sendStatus(401);
      return;
    }
    throw error;
  }
});

Pièges

  • body doit être le corps brut de la requête, exactement tel que reçu (avant tout JSON.parse/reformattage) : le sha256 est calculé sur ces octets précis, un framework qui reparse puis re-sérialise le JSON avant de vous le passer casserait la vérification.
  • createInMemoryWebhookIdempotencyStore() garde son état en mémoire du processus : suffisant pour une seule instance, mais chaque instance d’un déploiement multi-processus/multi-machine aurait son propre magasin indépendant. Un déploiement multi-instance doit fournir son propre magasin implémentant WebhookIdempotencyStore (Redis, base partagée, …).
  • LegbaWebhookEvent.createdAt est en secondes (pas en millisecondes), comme computeParticipantSessions l’attend.
  • LegbaWebhookEvent.egressId n’est défini que pour les événements egress_* — le même identifiant que EgressInfo.egressId rendu par createEgressService, utile pour corréler un événement webhook à un enregistrement en cours sans avoir à sonder son statut en boucle.
  • raw est une échappatoire expérimentale : WebhookReceiver n’expose que receive(), déjà enveloppé par verifyWebhook/processWebhook — utile surtout si une future version du SDK ajoute une méthode que WebhookService n’a pas encore rattrapée. Voir docs/recipes/raw-escape-hatch.md.

Voir aussi