# How to Configure OmniRoute Webhooks with HMAC Signing and Exponential Backoff

> Configure OmniRoute webhooks with HMAC signing and exponential backoff. Secure your deliveries with SHA-256 signing and ensure reliability with automatic retries.

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

---

**To configure OmniRoute webhooks with HMAC signing and exponential backoff, set a secret in your webhook record to enable SHA-256 payload signing, then use `notifyWebhookEvent` or `dispatchEvent` to trigger deliveries that automatically retry up to 3 times with exponential backoff.**

OmniRoute ships with a built-in webhook subsystem located in [`src/lib/webhookDispatcher.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhookDispatcher.ts) that delivers events to HTTP endpoints with cryptographic payload verification and resilient retry logic. Whether you are integrating with internal APIs or external services, understanding how to enable HMAC signing and configure the backoff behavior ensures reliable event delivery.

## Core Architecture

The webhook dispatcher in [`src/lib/webhookDispatcher.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhookDispatcher.ts) handles three critical concerns: cryptographic signing, transport resilience, and failure management. When you configure a webhook with a secret, the system uses **HMAC-SHA256** to sign every payload, while failed deliveries trigger an exponential backoff sequence.

### Payload Signing with HMAC-SHA256

When a webhook record includes a `secret` value, OmniRoute generates a signature using the `signPayload` helper (lines 20-22) and attaches it to the `X-Webhook-Signature` header. The dispatcher constructs the following headers for every signed request:

```typescript
const body = JSON.stringify(payload);
const headers: Record<string, string> = {
  "Content-Type": "application/json",
  "User-Agent": "OmniRoute-Webhook/1.0",
  "X-Webhook-Event": payload.event,
  "X-Webhook-Timestamp": payload.timestamp,
};

if (secret) {
  headers["X-Webhook-Signature"] = signPayload(body, secret);
}

```

The `signPayload` function computes a SHA-256 HMAC from the raw JSON body and your configured secret. Receiving servers should verify this signature to ensure payload authenticity.

### Exponential Backoff Retry Logic

Failed deliveries automatically retry using exponential backoff implemented in the `deliverWebhook` function (lines 71-129). The logic follows this pattern:

- **Retry condition**: HTTP status ≥ 500 or network errors
- **Delay calculation**: `2^n × 1000ms` where *n* is the current attempt count (0-indexed)
- **Maximum attempts**: Default of 3 retries (configurable via `maxRetries`)

```typescript
for (let attempt = 0; attempt <= maxRetries; attempt++) {
  try {
    const res = await fetch(url, { method: "POST", headers, body, signal });
    if (res.ok || res.status < 500) return { success: res.ok, status: res.status };
    // Server error triggers backoff
    if (attempt < maxRetries) {
      await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
    }
  } catch (error) {
    if (attempt === maxRetries) return { success: false, status: 0, error: error.message };
    await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
  }
}

```

Each request also respects a **10-second abort timeout** to prevent hanging connections.

## Setting Up Webhooks with HMAC Signing

To enable HMAC signing and exponential backoff for your endpoints, create a webhook record in the database (schema defined in [`src/lib/db/webhooks.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/webhooks.ts)) with the following configuration:

1. **Set the URL**: The destination HTTP endpoint
2. **Choose the kind**: Use `"custom"` for HMAC-signed payloads (Slack, Discord, and Telegram bypass signing)
3. **Configure the secret**: Store a random string in the `secret` column to activate HMAC signing
4. **Select events**: Specify event types (e.g., `"model.completed"`, `"quota.exhausted"`) or use `"*"` for all events
5. **Enable the webhook**: Ensure the `enabled` flag is `true`

The system persists delivery attempts in [`src/lib/db/webhookDeliveries.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/webhookDeliveries.ts) and automatically disables webhooks after 10 accumulated failures via `disableWebhooksWithHighFailures(10)`.

## Sending Events to Configured Webhooks

OmniRoute provides two APIs for dispatching events, both of which respect your HMAC and retry configuration.

### Fire-and-Forget Delivery

For hot paths where you cannot await network I/O, use `notifyWebhookEvent` (lines 137-144):

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

// Trigger after a model completes
notifyWebhookEvent("model.completed", {
  modelId: "gpt-4",
  requestId: "abc123",
  durationMs: 245,
});

```

This function returns immediately while the dispatcher asynchronously builds the payload, retrieves enabled webhooks, and calls `deliverWebhook` for each `custom` kind webhook with signing and retries.

### Awaitable Delivery

For CLI tools, tests, or synchronous flows, use `dispatchEvent` (lines 51-221) directly:

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

const result = await dispatchEvent("quota.exhausted", {
  accountId: "acct42",
  used: 999,
  limit: 1000
});

```

This awaitable method provides granular control and returns the delivery outcome, including any final error states after exhaustion of the exponential backoff retries.

## Handling Integration-Specific Formats

While the HMAC signing flow applies to `kind: "custom"` webhooks, OmniRoute supports platform-specific formats that bypass signing:

- **Slack**: Uses [`src/lib/webhooks/integrations/slack.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/integrations/slack.ts) to format payloads
- **Discord**: Uses [`src/lib/webhooks/integrations/discord.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/integrations/discord.ts)
- **Telegram**: Uses [`src/lib/webhooks/integrations/telegram.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/integrations/telegram.ts) (decrypts bot tokens from `metadata_encrypted`)

These integrations use the `deliverRaw` helper (lines 39-68) which omits HMAC headers but retains the 10-second timeout.

## Summary

- **HMAC signing** is enabled by setting a `secret` on webhook records in [`src/lib/db/webhooks.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/webhooks.ts), which triggers the `signPayload` function to add `X-Webhook-Signature` headers.
- **Exponential backoff** retries failed deliveries (HTTP ≥ 500 or network errors) up to 3 times using delays of 1s, 2s, and 4s, implemented in `deliverWebhook`.
- **Dispatch APIs** include `notifyWebhookEvent` for non-blocking fire-and-forget usage and `dispatchEvent` for synchronous awaiting.
- **Automatic protection** disables webhooks after 10 delivery failures to prevent resource waste.
- **Platform integrations** (Slack, Discord, Telegram) use unsigned `deliverRaw` while custom webhooks use signed `deliverWebhook`.

## Frequently Asked Questions

### How do I verify the HMAC signature on my receiving server?

Extract the `X-Webhook-Signature` header and compute your own HMAC-SHA256 digest using the raw request body and your shared secret. Compare the computed signature with the header value using a timing-safe comparison function. OmniRoute generates this signature in [`src/lib/webhookDispatcher.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhookDispatcher.ts) lines 20-22 using the exact JSON stringification of the payload.

### Can I customize the number of retry attempts?

Yes, the `deliverWebhook` function accepts a `maxRetries` parameter that defaults to 3. When calling `dispatchEvent` directly, you can influence retry behavior by modifying the webhook configuration or implementing custom wrapper logic, though the public API exposes the default backoff sequence of `2^n × 1s` delays.

### Why is my webhook not receiving the `X-Webhook-Signature` header?

The header is only included for webhooks with `kind: "custom"` that have a non-empty `secret` column. If you configured the webhook as `slack`, `discord`, or `telegram`, the system uses `deliverRaw` (lines 39-68) which does not perform HMAC signing. Additionally, ensure the webhook is enabled and has not been auto-disabled due to 10 consecutive failures.

### What happens if the exponential backoff retries all fail?

After exhausting all retry attempts (default 3), the `deliverWebhook` function returns `{ success: false, status: 0, error: … }` and the failure is recorded in `webhookDeliveries`. If a webhook accumulates 10 total failures, the system automatically sets `enabled: false` via `disableWebhooksWithHighFailures(10)` to prevent further delivery attempts until manually re-enabled.