How OmniRoute Handles Webhook Dispatching: HMAC Signing, Exponential Backoff, and Auto‑Disabling
OmniRoute dispatches webhooks from src/lib/webhookDispatcher.ts using kind‑specific payload builders, signs custom payloads with HMAC‑SHA256, retries transient failures with exponential backoff, and automatically disables endpoints after 10 consecutive failures.
OmniRoute is an open‑source routing platform that implements a robust webhook delivery system to notify external services about internal events. The dispatcher supports multiple integration types—Slack, Discord, Telegram, and custom HTTP endpoints—each with specialized handling and security features. This article examines how the src/lib/webhookDispatcher.ts module orchestrates webhook dispatching, implements cryptographic signing, and maintains delivery reliability through intelligent retry and circuit‑breaker patterns.
Webhook Kinds and Payload Construction
When an internal event fires, dispatchEvent queries the database for enabled webhooks via getEnabledWebhooks and routes the payload according to the webhook’s kind:
- Slack & Discord – Structured messages are built by
buildSlackPayloadandbuildDiscordPayloadrespectively, then sent viadeliverRawwithout HMAC signing. - Telegram – The stored
urlcontains the chat ID, while the bot token resides encrypted inmetadata_encrypted. The dispatcher decrypts the token usingdecryptMetadata, constructs the API URL withbuildTelegramUrl, formats the message withbuildTelegramPayload, and delivers viadeliverRaw. - Custom – The payload is wrapped in a canonical
WebhookPayloadobject containingevent,timestamp, anddata. If asecretis configured, the request is signed before transmission.
HMAC Signing for Custom Webhooks
Custom webhooks support cryptographic verification via signPayload(payload, secret). This function uses Node.js crypto.createHmac to generate a signature of the form sha256=<hex>.
The signature is attached to the request header X‑Webhook‑Signature, allowing recipients to verify integrity and authenticity. When combined with the X‑Webhook‑Event and X‑Webhook‑Timestamp headers, downstream services can detect replay attacks and tampering.
// Example headers sent to custom endpoints
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=8f2c...
Exponential Backoff and Retry Logic
The deliverWebhook function (lines 71‑128) implements resilient delivery with the following behavior:
- Attempt Limits: It performs up to
maxRetriesattempts (default 3). - Timeout Handling: Each attempt uses a fresh
AbortControllerwith a 10‑second timeout. - Status Code Handling: Success (OK) or client errors (
status < 500) return immediately. Server errors (status >= 500) and network failures trigger retries. - Backoff Calculation: Wait time follows
2^n × 1000ms, wherenis the attempt index (0‑based), creating delays of 1s, 2s, and 4s before giving up. - Final Error: After exhausting attempts, it returns a "Max retries exceeded" error.
This pattern ensures temporary outages or network blips do not permanently fail valid notifications while preventing infinite loops.
Auto‑Disabling Faulty Endpoints
After dispatchEvent settles all deliveries via Promise.allSettled, it invokes disableWebhooksWithHighFailures(10) from src/lib/db/webhooks.ts. This helper scans recent delivery records and disables any webhook whose failure count exceeds the threshold of 10.
Disabled webhooks are omitted from future getEnabledWebhooks queries, effectively acting as a circuit breaker. This prevents the system from wasting resources on permanently broken endpoints and reduces noise for operators.
Practical Usage Examples
Fire‑and‑forget notifications for hot paths like model processing loops:
import { notifyWebhookEvent } from "@/src/lib/webhookDispatcher";
// Non-blocking notification
notifyWebhookEvent("model_combo_failed", { comboId, reason });
Awaiting delivery for administrative tools or testing:
import { dispatchEvent } from "@/src/lib/webhookDispatcher";
// Waits for all webhook kinds to complete
await dispatchEvent("quota_exceeded", { userId, used: 1050 });
Configuring a custom webhook with HMAC verification:
INSERT INTO webhooks (url, secret, kind, events, enabled)
VALUES (
'https://example.com/omni-hook',
'super-secret-key',
'custom',
'["model_used"]',
true
);
Summary
- OmniRoute handles webhook dispatching in
src/lib/webhookDispatcher.tswith support for Slack, Discord, Telegram, and custom HTTP endpoints. - Custom webhooks receive HMAC‑SHA256 signatures via
signPayload, transmitted in theX‑Webhook‑Signatureheader for downstream verification. - Retry logic in
deliverWebhookuses exponential backoff (2^n × 1000ms) with a default of 3 attempts and 10‑second timeouts per try. - Auto‑disabling via
disableWebhooksWithHighFailures(10)acts as a circuit breaker, disabling webhooks after 10 consecutive failures to prevent endless retries. - The system distinguishes between client errors (immediate failure) and server/network errors (retry eligible), optimizing for both reliability and speed.
Frequently Asked Questions
How does OmniRoute verify webhook authenticity?
OmniRoute uses HMAC‑SHA256 signing for custom webhooks. The signPayload function creates a hash using the configured secret, formatting it as sha256=<hex> and attaching it to the X‑Webhook‑Signature header. Receivers can verify this signature against the request body using their copy of the secret.
What happens when a webhook endpoint returns a 500 error?
Server‑error responses (HTTP 5xx) trigger the exponential backoff retry mechanism in deliverWebhook. The dispatcher waits 1 second, then 2 seconds, then 4 seconds across three attempts before abandoning. After 10 total recorded failures, disableWebhooksWithHighFailures automatically disables the webhook.
Why are Slack and Discord webhooks not signed with HMAC?
Slack and Discord use platform‑specific payload formats and rely on their own security models (such as signed secrets or verification tokens managed by those platforms). OmniRoute sends these via deliverRaw without additional HMAC layers, as the integrity is handled at the platform level.
How can I prevent OmniRoute from retrying failed deliveries?
The system automatically stops retrying individual deliveries after 3 attempts. To prevent further attempts entirely, the webhook will be automatically disabled after accumulating 10 failures. You can also manually disable webhooks in the database by setting enabled = false, which removes them from the getEnabledWebhooks result set used by dispatchEvent.
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 →