How OmniRoute's Webhook Dispatcher Handles HMAC-Signed Delivery with Exponential Backoff

OmniRoute's webhook dispatcher signs custom webhook payloads with HMAC-SHA256 and retries failed deliveries using exponential backoff, automatically disabling endpoints that exceed 10 failures.

The webhook dispatcher in OmniRoute provides a resilient, secure mechanism for delivering event notifications to external endpoints. Located primarily in src/lib/webhookDispatcher.ts, this system handles multiple integration types while ensuring cryptographic integrity for custom webhooks through HMAC signing and fault tolerance via intelligent retry logic.

Webhook Dispatcher Architecture

OmniRoute’s dispatch pipeline centers around the dispatchEvent function in src/lib/webhookDispatcher.ts. When internal events fire, the system queries enabled webhooks via getEnabledWebhooks and routes payloads based on the webhook kind:

  • Slack & Discord – Formatted by buildSlackPayload or buildDiscordPayload and sent via deliverRaw without HMAC signing.
  • Telegram – Uses encrypted bot tokens from metadata_encrypted (decrypted via decryptMetadata) and delivered through deliverRaw.
  • Custom – Wrapped in a WebhookPayload object and optionally signed with HMAC-SHA256 via deliverWebhook.

HMAC-SHA256 Signature Generation

For custom webhooks configured with a secret, OmniRoute generates an HMAC-SHA256 signature to ensure payload authenticity. The signPayload(payload, secret) function (lines 90-92) uses Node.js crypto.createHmac to produce a digest in the format sha256=<hex>.

This signature attaches to the X-Webhook-Signature header, enabling receivers to verify the request originated from OmniRoute and was not tampered with in transit.

Exponential Backoff and Retry Logic

The deliverWebhook function (lines 71-128) implements resilient delivery with the following characteristics:

  1. Retry Limit: Performs up to maxRetries attempts (default 3).
  2. Timeout Protection: Each attempt uses a fresh AbortController with a 10-second timeout (lines 96-98).
  3. Selective Retries:
    • Success or client errors (HTTP < 500): Returns immediately (lines 13-15).
    • Server errors or network failures: Triggers the backoff sequence.
  4. Backoff Calculation: Waits 2^n × 1000ms where n equals the attempt index (lines 17-24).
  5. Final Failure: After exhausting retries, returns a "Max retries exceeded" error (line 28).

Both network exceptions and 5xx responses initiate the exponential delay, preventing thundering herds while maintaining delivery reliability.

Automatic Endpoint Disabling

To prevent endless retry loops against permanently broken endpoints, OmniRoute implements circuit-breaker logic. After dispatchEvent settles all deliveries via Promise.allSettled (line 19), it invokes disableWebhooksWithHighFailures(10) (line 20).

This helper, defined in src/lib/db/webhooks.ts, scans recent delivery records and disables any webhook exceeding 10 failures. Disabled webhooks are excluded from future dispatchEvent runs until manually re-enabled.

Integration-Specific Delivery Paths

Different webhook kind values receive specialized handling:

Code Examples

Dispatching an event with automatic retry handling:

import { dispatchEvent } from "@/src/lib/webhookDispatcher";

// This triggers the full pipeline: HMAC signing, exponential backoff, and auto-disabling checks
await dispatchEvent("model_used", { modelId: "gpt-4", tokens: 150 });

Custom webhook configuration with HMAC secret:

-- Database configuration for a signed webhook
INSERT INTO webhooks (url, secret, kind, events, enabled) 
VALUES (
  'https://api.example.com/webhooks/omniroute', 
  'whsec_super_secret_key', 
  'custom', 
  '["model_used", "quota_exceeded"]', 
  true
);

Resulting headers sent to the endpoint:


Content-Type: application/json
User-Agent: OmniRoute-Webhook/1.0
X-Webhook-Event: model_used
X-Webhook-Timestamp: 2026-07-14T12:34:56.789Z
X-Webhook-Signature: sha256=8f2c3d4e5f6...

Summary

  • HMAC Signing: Custom webhooks use signPayload to generate SHA256 signatures in X-Webhook-Signature headers via Node.js crypto.createHmac.
  • Exponential Backoff: deliverWebhook retries failed requests up to 3 times with delays calculated as 2^n × 1000ms for server errors and network failures.
  • Auto-Disabling: The system disables webhooks exceeding 10 delivery failures, preventing resource waste on dead endpoints.
  • Type Safety: Separate handling paths for Slack, Discord, Telegram, and Custom webhooks ensure appropriate formatting and security levels.

Frequently Asked Questions

What triggers the exponential backoff in OmniRoute's webhook dispatcher?

The backoff triggers on HTTP 5xx server errors or network-level exceptions such as timeouts or connection resets. Client errors (4xx responses) do not trigger retries and return immediately, as these typically indicate authentication or validation failures that won't resolve with repetition.

How does OmniRoute verify webhook payload integrity?

For custom webhooks configured with a secret, OmniRoute generates an HMAC-SHA256 hash of the payload using Node.js crypto.createHmac. It transmits this as X-Webhook-Signature: sha256=<hex> (lines 90-92 in src/lib/webhookDispatcher.ts), allowing receiving services to validate the signature against their copy of the shared secret.

Why do Slack and Discord webhooks not use HMAC signing?

Slack and Discord integrations use platform-specific formatting functions (buildSlackPayload, buildDiscordPayload) and rely on deliverRaw for transmission. These platforms typically use other authentication methods (like OAuth tokens or signed URLs) embedded in the webhook URL itself, making additional HMAC headers unnecessary for OmniRoute's implementation.

How can I prevent OmniRoute from disabling my webhook during temporary outages?

OmniRoute disables webhooks only after accumulating 10 recorded failures via disableWebhooksWithHighFailures. To avoid this during planned maintenance, temporarily disable the webhook in the database or ensure your endpoint returns 2xx status codes even if temporarily unable to process the payload, storing it for asynchronous handling instead.

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 →