/**
* 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 {};