// webhook-utils.js (VERSION ES MODULES) // Pour projets avec "type": "module" dans package.json import axios from 'axios'; import crypto from 'crypto'; class WebhookManager { constructor(secretKey) { this.secretKey = secretKey; } /** * Génère une signature HMAC SHA-256 pour sécuriser le webhook * @param {Object} payload - Les données à signer * @returns {string} La signature hexadécimale */ generateSignature(payload) { return crypto .createHmac('sha256', this.secretKey) .update(JSON.stringify(payload)) .digest('hex'); } /** * Vérifie la signature d'un webhook reçu * @param {Object} payload - Les données reçues * @param {string} receivedSignature - La signature reçue dans le header * @returns {boolean} True si la signature est valide */ verifySignature(payload, receivedSignature) { if (!receivedSignature) { console.error('❌ Aucune signature fournie'); return false; } try { const expectedSignature = this.generateSignature(payload); return crypto.timingSafeEqual( Buffer.from(expectedSignature), Buffer.from(receivedSignature) ); } catch (error) { console.error('❌ Erreur vérification signature:', error); return false; } } /** * Envoie un webhook à une URL cible avec retry automatique * @param {string} targetUrl - URL du serveur cible * @param {string} eventType - Type d'événement (ex: 'demande.validated') * @param {Object} data - Données de l'événement * @param {number} retries - Nombre de tentatives (défaut: 3) * @returns {Promise} La réponse du serveur */ async sendWebhook(targetUrl, eventType, data, retries = 3) { const payload = { event: eventType, data: data, timestamp: new Date().toISOString() }; const signature = this.generateSignature(payload); for (let attempt = 1; attempt <= retries; attempt++) { try { console.log(`📤 Envoi webhook: ${eventType} vers ${targetUrl} (tentative ${attempt}/${retries})`); const response = await axios.post( `${targetUrl}/api/webhook/receive`, payload, { headers: { 'Content-Type': 'application/json', 'X-Webhook-Signature': signature }, timeout: 5000 // 5 secondes de timeout } ); console.log(`✅ Webhook envoyé avec succès: ${eventType}`); return response.data; } catch (error) { console.error(`❌ Erreur envoi webhook (tentative ${attempt}/${retries}):`, error.message); if (attempt === retries) { console.error(`❌ Échec définitif du webhook après ${retries} tentatives`); throw error; } // Attendre avant de réessayer (backoff exponentiel) const waitTime = 1000 * attempt; console.log(`⏳ Nouvelle tentative dans ${waitTime}ms...`); await new Promise(resolve => setTimeout(resolve, waitTime)); } } } /** * Envoie un webhook sans attendre la réponse (fire and forget) * Utile pour ne pas bloquer l'exécution * @param {string} targetUrl - URL du serveur cible * @param {string} eventType - Type d'événement * @param {Object} data - Données de l'événement */ sendWebhookAsync(targetUrl, eventType, data) { this.sendWebhook(targetUrl, eventType, data) .catch(error => { console.error('❌ Webhook async échoué (non bloquant):', error.message); }); } } export default WebhookManager;