// Receptor de webhooks do Hotel das Lojas: confere a assinatura e mostra cada evento. // Uso: HDL_WEBHOOK_SEGREDO=... node receptor-webhook.mjs (porta 3000; exponha com https, ex.: cloudflared, ngrok) import http from 'node:http'; import crypto from 'node:crypto'; const SEGREDO = process.env.HDL_WEBHOOK_SEGREDO; if (!SEGREDO) { console.error('Defina HDL_WEBHOOK_SEGREDO (aparece ao criar o webhook).'); process.exit(1); } const vistos = new Set(); // ignora repeticoes do mesmo evento http.createServer((req, res) => { let corpo = ''; req.on('data', (c) => (corpo += c)); req.on('end', () => { const ts = req.headers['x-hdl-timestamp']; const recebida = String(req.headers['x-hdl-assinatura'] || '').replace('sha256=', ''); const esperada = crypto.createHmac('sha256', SEGREDO).update(`${ts}.${corpo}`).digest('hex'); const valida = recebida.length === esperada.length && crypto.timingSafeEqual(Buffer.from(recebida), Buffer.from(esperada)) && Math.abs(Date.now() / 1000 - Number(ts)) < 300; if (!valida) { res.writeHead(401).end(); return console.log('✘ assinatura inválida'); } const evento = JSON.parse(corpo); res.writeHead(200, { 'Content-Type': 'application/json' }).end('{"ok":true}'); // responda rapido; processe depois if (vistos.has(evento.id)) return; vistos.add(evento.id); console.log(`✔ ${evento.evento} · pedido ${evento.dados?.id ?? '-'} · ${evento.dados?.status ?? ''}`); }); }).listen(3000, () => console.log('Esperando webhooks em http://localhost:3000'));