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
buildSlackPayloadorbuildDiscordPayloadand sent viadeliverRawwithout HMAC signing. - Telegram – Uses encrypted bot tokens from
metadata_encrypted(decrypted viadecryptMetadata) and delivered throughdeliverRaw. - Custom – Wrapped in a
WebhookPayloadobject and optionally signed with HMAC-SHA256 viadeliverWebhook.
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:
- Retry Limit: Performs up to
maxRetriesattempts (default 3). - Timeout Protection: Each attempt uses a fresh
AbortControllerwith a 10-second timeout (lines 96-98). - Selective Retries:
- Success or client errors (HTTP < 500): Returns immediately (lines 13-15).
- Server errors or network failures: Triggers the backoff sequence.
- Backoff Calculation: Waits
2^n × 1000mswherenequals the attempt index (lines 17-24). - 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:
- Slack: Formatted via
src/lib/webhooks/integrations/slack.tsusing block-kit structures. - Discord: Processed by
src/lib/webhooks/integrations/discord.tswith embed formatting. - Telegram: Constructed in
src/lib/webhooks/integrations/telegram.ts, decrypting the bot token to build the URL dynamically. - Custom: The only type receiving HMAC signatures, using the full
WebhookPayloadschema withevent,timestamp, anddatafields.
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
signPayloadto generate SHA256 signatures inX-Webhook-Signatureheaders via Node.jscrypto.createHmac. - Exponential Backoff:
deliverWebhookretries failed requests up to 3 times with delays calculated as2^n × 1000msfor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →