How the Webhook Dispatcher in OmniRoute Works: Event Taxonomy, Retry Logic, and Secret Rotation

The Webhook Dispatcher in OmniRoute is a centralized delivery engine that filters events by taxonomy, signs payloads with HMAC-SHA256, implements exponential backoff retry logic with a 10-second timeout, and automatically disables endpoints after 10 consecutive failures.

The Webhook Dispatcher in OmniRoute orchestrates real-time event delivery to external endpoints using a layered architecture that separates event taxonomy, payload construction, and delivery resilience. Written in TypeScript, this system handles everything from LLM request completions to quota alerts, ensuring secure transmission through signed payloads and intelligent retry mechanisms. Understanding its implementation reveals how the platform manages webhook subscriptions, transient network failures, and seamless secret rotation without requiring code changes.

Event Taxonomy and Subscription Filtering

OmniRoute defines a strict event taxonomy in src/lib/webhooks/eventDescriptions.ts, declaring the WebhookEvent type that supports four distinct events: request.completed, request.failed, quota.exceeded, and test.ping. Each event carries human-readable metadata through the accompanying EVENT_DESCRIPTIONS map, providing clear documentation for integrators.

The dispatcher filters subscriptions dynamically in dispatchEvent (line 68 of src/lib/webhookDispatcher.ts). When processing an event, the system checks each enabled webhook’s events array for either the specific event name or a wildcard "*": wh.events.includes("*") || wh.events.includes(event). This pattern enables both granular per-event subscriptions and catch-all configurations, ensuring endpoints receive only the traffic they explicitly request.

Payload Construction and HMAC Signing

When an event fires, the dispatcher constructs a uniform WebhookPayload object (lines 61‑65 of src/lib/webhookDispatcher.ts) containing the event name, ISO timestamp, and event-specific data blob. For custom webhooks, the system invokes deliverWebhook (lines 71‑129), which handles provider-agnostic delivery with cryptographic verification.

Custom webhooks receive HMAC-SHA256 signed payloads. In createWebhook (lines 94‑95 of src/lib/db/webhooks.ts), secrets are either user-supplied or automatically generated with a whsec_ prefix. The signPayload helper (lines 20‑22) adds the X-Webhook-Signature header only when wh.secret exists, computed at request time using the current secret value. Built-in integrations (Slack, Discord, Telegram) bypass this signing flow; instead, deliverRaw (lines 39‑68) transforms payloads via provider-specific builder modules in src/lib/webhooks/integrations/.

Retry Semantics and Delivery Resilience

The deliverWebhook function implements configurable exponential backoff with a default maxRetries of 3 (line 75). The retry loop (lines 94‑126) executes the following logic for each attempt:

  1. Abort Handling: Initializes an AbortController with a 10‑second timeout (lines 96‑98) to prevent hanging connections.
  2. Request Execution: Performs the POST request to the webhook URL.
  3. Success Criteria: Returns immediately on HTTP 2xx success or non‑5xx client errors (4xx responses trigger immediate failure without retry).
  4. Backoff Calculation: On 5xx server errors or network failures, waits 2^attempt seconds before retrying (lines 17‑19).

If all retries exhaust, the function returns { success: false, error: "..." } (line 28), triggering failure recording and health check evaluation.

Automatic Health Monitoring and Endpoint Disabling

After each delivery attempt, recordWebhookDelivery persists the result and updates success or failure counters in the database. The dispatcher then calls disableWebhooksWithHighFailures(10) (line 20 of dispatchEvent), which automatically disables any webhook accumulating 10 or more consecutive failures. This circuit-breaker pattern prevents the system from repeatedly hammering misconfigured or downed endpoints, preserving system resources and reducing noise.

Secret Rotation Without Downtime

Secret rotation requires zero code changes in OmniRoute. Administrators update the secret column via the updateWebhook API, and subsequent deliveries automatically use the new value because signing occurs at request time rather than at configuration time. This design eliminates the need for rolling deployments or service restarts when rotating compromised or expired credentials.

Practical Implementation Examples

To fire a webhook event after a successful LLM inference:

import { notifyWebhookEvent } from "@/lib/webhookDispatcher";

const event = "request.completed";
const data = {
  model: "claude-opus-4-7",
  provider: "claude",
  latencyMs: 1240,
  tokensIn: 142,
  tokensOut: 38,
};

notifyWebhookEvent(event, data);

To register a new custom endpoint with explicit secret management:

import { createWebhook } from "@/lib/db/webhooks";

const webhook = createWebhook({
  url: "https://example.com/omniroute-hook",
  events: ["request.completed", "request.failed"],
  secret: "my-very-secret-key", // Rotate by updating this value later
  description: "Audit log endpoint",
  kind: "custom",
});

Summary

  • Event Taxonomy: Four typed events (request.completed, request.failed, quota.exceeded, test.ping) defined in src/lib/webhooks/eventDescriptions.ts, with wildcard "*" support for catch-all subscriptions.
  • Signed Payloads: Custom webhooks receive HMAC-SHA256 signatures in the X-Webhook-Signature header, computed at request time using secrets from src/lib/db/webhooks.ts.
  • Retry Logic: Exponential backoff (2^attempt seconds) with a 10-second timeout, defaulting to 3 retries, treating 4xx errors as permanent failures and 5xx/network errors as transient.
  • Health Monitoring: Automatic disabling of webhooks after 10 consecutive failures via disableWebhooksWithHighFailures(10).
  • Secret Rotation: Immediate effect upon database update without requiring code changes or restarts.

Frequently Asked Questions

What event types does the OmniRoute Webhook Dispatcher support?

The dispatcher supports four core events defined in src/lib/webhooks/eventDescriptions.ts: request.completed for successful LLM inferences, request.failed for errors, quota.exceeded for rate-limit breaches, and test.ping for connectivity verification. Endpoints can subscribe to specific events or use the "*" wildcard to receive all events.

How does the retry logic handle different HTTP status codes?

According to the implementation in src/lib/webhookDispatcher.ts (lines 13‑15), the dispatcher treats HTTP 4xx client errors as permanent failures that do not trigger retries, while 5xx server errors and network timeouts initiate exponential backoff. This prevents wasted retries on authentication failures (401/403) or bad requests (400) while resiliently handling temporary server outages.

What triggers automatic webhook disabling in OmniRoute?

The system automatically disables webhooks that accumulate 10 or more consecutive failures. After each delivery attempt, disableWebhooksWithHighFailures(10) evaluates the failure count tracked in src/lib/db/webhooks.ts and disables the endpoint if the threshold is exceeded, acting as a circuit breaker to protect both OmniRoute and the receiving server.

How do you rotate a webhook secret without downtime?

Administrators simply call the updateWebhook API to change the secret column value in the database. Since the deliverWebhook function reads the secret at request time and computes the HMAC signature dynamically (lines 20‑22), the new secret takes effect immediately on the next delivery without requiring code deployments or service restarts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →