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
| Nom | Type | Requis | Défaut | Description |
|---|---|---|---|---|
apiKey | string | oui | — | Clé API LiveKit (celle qui a signé le webhook) |
apiSecret | string | oui | — | Secret API LiveKit |
Retour
Un WebhookService :
| Méthode | Rô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 }. |
raw | Instance réelle WebhookReceiver du SDK — échappatoire typée, voir docs/recipes/raw-escape-hatch.md |
Erreurs
| Code | Quand | Comment corriger |
|---|---|---|
LEGBA_WEBHOOK_INVALID_SIGNATURE | En-tête Authorization absent, JWT invalide/expiré, ou sha256 du corps ne correspondant pas | Vé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
bodydoit être le corps brut de la requête, exactement tel que reçu (avant toutJSON.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émentantWebhookIdempotencyStore(Redis, base partagée, …).LegbaWebhookEvent.createdAtest en secondes (pas en millisecondes), commecomputeParticipantSessionsl’attend.LegbaWebhookEvent.egressIdn’est défini que pour les événementsegress_*— le même identifiant queEgressInfo.egressIdrendu parcreateEgressService, utile pour corréler un événement webhook à un enregistrement en cours sans avoir à sonder son statut en boucle.rawest une échappatoire expérimentale :WebhookReceivern’expose quereceive(), déjà enveloppé parverifyWebhook/processWebhook— utile surtout si une future version du SDK ajoute une méthode queWebhookServicen’a pas encore rattrapée. Voirdocs/recipes/raw-escape-hatch.md.