Sécurité
Signature ou jeton
Les deux se règlent côté service, indépendamment l'un de l'autre, et peuvent être exigés ensemble : une livraison doit alors satisfaire les deux. Sans l'un ni l'autre, le récepteur accepte toute livraison.
- La signature (
secret) : le service envoie dansx-lfp-signature-256une empreinte HMAC SHA-256 du corps. Le secret ne circule jamais, et la signature atteste que le corps n'a pas été modifié en route. C'est le choix à faire dès que la livraison traverse le réseau. - Le jeton (
token) : attendu dansAuthorization: Bearer <token>. Plus simple, mais il voyage à chaque requête : à réserver à une liaison locale ou chiffrée.
createReceiver({
secret: process.env.LFP_WEBHOOKS_SECRET,
token: process.env.LFP_WEBHOOKS_TOKEN,
})
Les deux comparaisons se font en temps constant. Une livraison refusée reçoit 401, un corps illisible 400.
La signature porte sur les octets reçus : le corps doit arriver brut jusqu'au récepteur, jamais analysé puis re-sérialisé (voir Express et Fastify).
Répondre avant de traiter
Le service abandonne une livraison au bout de dix secondes et la retente. Le récepteur répond donc 200 dès que la livraison est vérifiée, sans attendre vos gestionnaires, qui s'exécutent ensuite.
Doublons
Une livraison retentée garde le même identifiant x-lfp-delivery. Le récepteur retient les 512 derniers (dedupeSize) : une livraison déjà vue reçoit un accusé de réception, sans que les gestionnaires ne soient rappelés. dedupeSize: 0 désactive ce filtre.
Cette mémoire vit dans le processus : après un redémarrage, ou avec plusieurs instances derri ère un répartiteur, utilisez context.id comme clé d'idempotence dans votre propre stockage si un doublon pose problème.
Ordre
Les gestionnaires s'exécutent en file, un après l'autre et dans l'ordre d'arrivée des livraisons, même quand ils sont asynchrones. Une erreur dans un gestionnaire est transmise à onError et n'interrompt pas la file.
createReceiver({
secret: process.env.LFP_WEBHOOKS_SECRET,
onError: (error, context) => logger.error({ error, event: context.event, id: context.id }),
})