/** * Webhook Dispatcher * * Delivers events to configured webhook endpoints with retry logic. */ /// import type { SystemEvent } from "../events/event-types.js"; import type { WebhookConfig, WebhookStats, AddWebhookInput, UpdateWebhookInput } from "./types.js"; type WebhookUrlValidation = { ok: true; url: URL; } | { ok: false; error: string; }; /** * Validate a webhook URL before we ever send it an outbound request. * * Checks (in order): * 1. Parseable URL * 2. Scheme: require https:; allow http: only when NLMCP_WEBHOOK_ALLOW_HTTP=true * 3. Lexical hostname in private/loopback/link-local/metadata space * 4. DNS resolution — all resolved addresses must be public (closes * DNS-rebinding attacks); skipped when NLMCP_WEBHOOK_RESOLVE_DNS=false * * Exported for use by tool handlers that want to pre-validate before * calling dispatcher.addWebhook(). */ export declare function validateWebhookUrl(rawUrl: string): Promise; export declare class WebhookDispatcher { private storePath; private deliveryLogPath; private store; private unsubscribe; private deliveryHistory; private maxDeliveryHistory; private readonly circuitBreakerThreshold; private readonly circuitBreakerResetMs; private circuitBreakers; private deliverySequence; private webhookSecrets; private saveQueue; constructor(); /** * Load webhooks from disk */ private loadStore; /** * Save webhooks to disk */ private saveStore; /** * Initialize webhooks from environment variables. Each URL is validated * via validateWebhookUrl before being stored — invalid env values log a * warning and are skipped (server must still start). */ private initializeFromEnv; /** * Subscribe to all events */ private subscribeToEvents; /** * Dispatch an event to all matching webhooks in parallel. * * Using Promise.allSettled so one slow/failing webhook does not block * or cancel delivery to others (I275). */ dispatch(event: SystemEvent): Promise; /** * Check if webhook should receive this event type */ private shouldSend; private getCircuitBreakerState; private shouldSkipForOpenCircuit; private onDeliverySuccess; private onDeliveryFailure; private logAttempt; /** * Send event with retry logic. * * Retries capped at 3 attempts with max 30 s total window (I276). * Payload is secrets-scanned before dispatch (I273). * Outbound headers filtered to remove dangerous overrides (I282). * HMAC signature includes unix timestamp to prevent replay (I271). */ private sendWithRetry; /** * Format event payload for different platforms */ private formatPayload; /** * Format for Slack */ private formatSlack; /** * Format for Discord */ private formatDiscord; /** * Format for Microsoft Teams */ private formatTeams; /** * Get emoji for event type */ private getEmoji; /** * Get color for event type (Discord embed color) */ private getColor; /** * Get title for event */ private getTitle; /** * Get description for event */ private getDescription; /** * Sign payload with HMAC-SHA256, including a unix timestamp in the signed * data so receivers can reject replayed requests (I271). * Signed message: "\n" */ private sign; /** * Load recent delivery history from disk on startup (I279) */ private loadDeliveryHistory; /** * Record delivery for history — persists to disk for cross-restart auditability (I279) */ private recordDelivery; private nextDeliverySequence; /** * Add a new webhook. Validates the URL before persisting; throws on * scheme/host/DNS-resolution failure so callers see a clear reason. * Records a ChangeLog entry for SOC2 change-management audit trail. */ addWebhook(input: AddWebhookInput): Promise; /** * Update a webhook. Re-validates the URL if it is being changed. * Records a ChangeLog entry for SOC2 change-management audit trail. */ updateWebhook(input: UpdateWebhookInput): Promise; /** * Remove a webhook. * Records a ChangeLog entry for SOC2 change-management audit trail. */ removeWebhook(id: string): Promise; /** * Helper: extract just the host from a URL for audit records. Never * log the full URL (may contain secret tokens as path components, as * Slack/Discord do). */ private safeHost; /** * Helper: write a ChangeLog entry for webhook CRUD. Errors are * swallowed with a warning — the webhook change itself has already * succeeded and compliance logging must not break the caller. */ private recordWebhookChange; /** * List all webhooks */ listWebhooks(): WebhookConfig[]; /** * Get a specific webhook */ getWebhook(id: string): WebhookConfig | null; /** * Test a webhook */ testWebhook(id: string): Promise<{ success: boolean; error?: string; }>; /** * Get webhook statistics */ getStats(): WebhookStats; /** * Cleanup */ destroy(): void; } export declare function getWebhookDispatcher(): WebhookDispatcher; export {};