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 buildSlackPayload and buildDiscordPayload respectively, then sent via deliverRaw without HMAC signing.
  • Telegram – The stored url contains the chat ID, while the bot token resides encrypted in metadata_encrypted. The dispatcher decrypts the token using decryptMetadata, constructs the API URL with buildTelegramUrl, formats the message with buildTelegramPayload, and delivers via deliverRaw.
  • Custom – The payload is wrapped in a canonical WebhookPayload object containing event, timestamp, and data. If a secret is 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:

  1. Attempt Limits: It performs up to maxRetries attempts (default 3).
  2. Timeout Handling: Each attempt uses a fresh AbortController with a 10‑second timeout.
  3. Status Code Handling: Success (OK) or client errors (status < 500) return immediately. Server errors (status >= 500) and network failures trigger retries.
  4. Backoff Calculation: Wait time follows 2^n × 1000 ms, where n is the attempt index (0‑based), creating delays of 1s, 2s, and 4s before giving up.
  5. 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.ts with support for Slack, Discord, Telegram, and custom HTTP endpoints.
  • Custom webhooks receive HMAC‑SHA256 signatures via signPayload, transmitted in the X‑Webhook‑Signature header for downstream verification.
  • Retry logic in deliverWebhook uses exponential backoff (2^n × 1000 ms) 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:

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 →