// Standalone receiver boundary. No WingsFlow application imports or database access. import { createHmac, timingSafeEqual } from 'node:crypto'; export function verifyWebhook( raw, headers, secrets, now = Math.floor(Date.now() / 1000), ) { const timestamp = headers.get('wingsflow-timestamp') || '', delivery = headers.get('wingsflow-delivery-id') || '', event = headers.get('wingsflow-event-id') || ''; if ( !/^\d{10}$/.test(timestamp) || Math.abs(now - Number(timestamp)) > 300 || ![delivery, event].every((id) => /^[0-9a-f-]{36}$/i.test(id)) ) return false; const received = (headers.get('wingsflow-signature') || '') .split(',') .map((v) => v.trim()) .filter((v) => /^v1=[a-f0-9]{64}$/.test(v)) .map((v) => Buffer.from(v.slice(3), 'hex')); return secrets.some((secret) => { const expected = createHmac('sha256', secret) .update(`${timestamp}.${delivery}.${event}.`) .update(raw) .digest(); return received.some((value) => timingSafeEqual(value, expected)); }); } export function readVerifiedEvent(raw, headers, secrets, tenantId) { if (!verifyWebhook(raw, headers, secrets)) throw Error('Invalid webhook signature'); const event = JSON.parse(raw.toString('utf8')); if ( event.id !== headers.get('wingsflow-event-id') || event.tenant_id !== tenantId || event.schema_version !== '1' ) throw Error('Unexpected event identity'); return event; } // Validate event against the supplied OpenAPI webhook schema, commit a unique // (tenant_id,event.id) inbox record in YOUR database, then acknowledge 2xx. // On endpoint.verification, return exactly 200 {challenge:event.data.challenge}. // Process the inbox asynchronously; retain event IDs >=14 days and reject older // resource.version updates. This helper deliberately does not fake durable storage.