// Outbound webhook delivery. emit(event, tenantId, payload) fans the event out to every // active per-tenant subscription registered for that event, plus the legacy global // BIZGAZE_WEBHOOK_URL (back-compat). Each delivery is HMAC-signed and retried on failure. // // NOTE (roadmap): retries are in-memory/best-effort. For guaranteed delivery this should // move to a persistent queue when the app scales to multiple instances (see ARCHITECTURE.md). const R = require('./repos'); const crypto = require('crypto'); const EVENTS = ['session.started', 'session.ended']; function sign(secret, body) { return crypto.createHmac('sha256', secret || '').update(body).digest('base64url'); } const RETRY_DELAYS = [2000, 10000, 30000]; // after the first attempt function deliver(url, secret, body, onDone) { let attempt = 0; const go = async () => { attempt++; let ok = false, status = 0, err = null; try { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-BizGaze-Signature': sign(secret, body), 'X-BizGaze-Event': (() => { try { return JSON.parse(body).event; } catch { return ''; } })() }, body, signal: AbortSignal.timeout(10000), }); status = res.status; ok = res.ok; } catch (e) { err = (e && e.message) || 'delivery failed'; } if (ok || attempt > RETRY_DELAYS.length) { if (onDone) onDone({ ok, status, err }); return; } setTimeout(go, RETRY_DELAYS[attempt - 1]); }; go(); } function emit(event, tenantId, payload) { const body = JSON.stringify({ event, ...payload }); // Per-tenant subscriptions try { for (const h of R.webhooks.activeForTenant(tenantId)) { const subs = String(h.events || '').split(',').map((s) => s.trim()); if (subs.includes('*') || subs.includes(event)) { deliver(h.url, h.secret, body, (r) => { try { R.webhooks.setStatus(h.id, r.ok ? 1 : 0, r.err || ('HTTP ' + r.status)); } catch (_) {} }); } } } catch (_) {} // Legacy global webhook (back-compat): session.ended → BIZGAZE_WEBHOOK_URL, signed with SSO_SECRET. if (event === 'session.ended' && process.env.BIZGAZE_WEBHOOK_URL) { deliver(process.env.BIZGAZE_WEBHOOK_URL, process.env.SSO_SECRET || '', body); } } module.exports = { emit, sign, EVENTS };