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

> Learn how OmniRoute's webhook dispatcher secures payloads with HMAC-SHA256, retries with exponential backoff, and disables failing endpoints to ensure reliable webhook delivery.

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

---

**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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/integrations/slack.ts) using block-kit structures.
- **Discord**: Processed by [`src/lib/webhooks/integrations/discord.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/integrations/discord.ts) with embed formatting.
- **Telegram**: Constructed in [`src/lib/webhooks/integrations/telegram.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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 `WebhookPayload` schema with `event`, `timestamp`, and `data` fields.

## Code Examples

*Dispatching an event with automatic retry handling:*

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

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