const http = require('http'); const crypto = require('crypto'); const secret = process.env.WEBHOOK_TEST_SECRET || ''; const received = []; let failuresRemaining = Number(process.env.WEBHOOK_FAIL_COUNT || 0); http.createServer((req, res) => { if (req.method === 'GET' && req.url === '/received') { res.writeHead(200, { 'content-type': 'application/json' }); return res.end(JSON.stringify(received)); } if (req.method === 'POST' && (req.url === '/events' || req.url === '/fail')) { const chunks = []; req.on('data', (chunk) => chunks.push(chunk)); req.on('end', () => { const body = Buffer.concat(chunks).toString(); const expected = `sha256=${crypto.createHmac('sha256', secret).update(body).digest('hex')}`; const signatureValid = !secret || req.headers['x-webhook-signature'] === expected; received.push({ path: req.url, signatureValid, body: JSON.parse(body), receivedAt: new Date().toISOString(), }); if (!signatureValid) { res.writeHead(401); return res.end('invalid signature'); } if (req.url === '/fail' && failuresRemaining-- > 0) { res.writeHead(503); return res.end('intentional failure'); } res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify({ accepted: true })); }); return; } res.writeHead(404); res.end('not found'); }).listen(8080, '0.0.0.0');