# How OmniRoute Handles Webhook Dispatching: HMAC Signing, Exponential Backoff, and Auto‑Disabling

> Discover how OmniRoute handles webhook dispatching with HMAC signing, exponential backoff retries, and automatic endpoint disabling to ensure reliable delivery.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-07-14

---

**OmniRoute dispatches webhooks from [`src/lib/webhookDispatcher.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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.

```typescript
// 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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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:

```typescript
import { notifyWebhookEvent } from "@/src/lib/webhookDispatcher";

// Non-blocking notification
notifyWebhookEvent("model_combo_failed", { comboId, reason });

```

Awaiting delivery for administrative tools or testing:

```typescript
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:

```sql
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`.