# How to Implement Webhooks in OmniRoute: Complete Developer Guide

> Implement webhooks in OmniRoute by defining records, specifying events like chat completion, and choosing integrations. This guide covers API setup, event routing, and automated retries for seamless integration.

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

---

**To implement webhooks in OmniRoute, define a webhook record in the SQLite database using the REST API or CLI, specify the events you want to monitor (such as `chat.completion` or `quota.exhausted`), and select an integration type (Slack, Telegram, Discord, or custom). The dispatcher automatically routes matching events through the appropriate integration module, handles retries with exponential backoff, and disables failing webhooks after a configurable threshold.**

OmniRoute provides a production-ready webhook subsystem that triggers external services based on internal events like model completions, quota changes, or MCP tool invocations. Whether you are building alerting for Slack or automated Telegram notifications, implementing webhooks in OmniRoute requires understanding its three-layer architecture: persistence, dispatch, and integration formatting. This guide references the actual source code from the `diegosouzapw/OmniRoute` repository to show you exactly how to configure, secure, and manage webhook deliveries.

## Webhook Architecture Overview

OmniRoute organizes its webhook system into distinct layers that handle data storage, event routing, and payload formatting.

### Persistence Layer

Webhook definitions live in SQLite via [`src/lib/db/webhooks.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/webhooks.ts). This module exports the `Webhook` type that defines the schema:

```typescript
// src/lib/db/webhooks.ts
export type Webhook = {
  id: string;               // auto-generated "wh-xxxxx"
  url: string;              // destination endpoint
  description?: string;
  enabled: boolean;
  events: string[];         // e.g. ["chat.completion", "quota.exhausted"]
  integration: "telegram" | "slack" | "discord" | "custom";
};

```

Delivery logs are tracked separately in [`src/lib/db/webhookDeliveries.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/webhookDeliveries.ts), which records every attempt with timestamps, success flags, and response metadata.

### Dispatcher Engine

The core delivery logic resides in [`src/lib/webhookDispatcher.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhookDispatcher.ts). The `deliverWebhook` function selects enabled hooks matching the event type, builds service-specific payloads, and executes safe HTTP POST requests:

```typescript
// src/lib/webhookDispatcher.ts
async function deliverWebhook(event: WebhookEvent) {
  const targets = db.getEnabledWebhooks().filter(w => w.events.includes(event.type));
  for (const hook of targets) {
    const payload = buildPayload(hook.integration, event);
    await postWithRetry(hook.url, payload, hook.id);
  }
}

```

All outbound requests pass a strict **SSRF guard** implemented in the same file, preventing attacks against internal network ranges. The `postWithRetry` utility implements exponential backoff and logs outcomes to the deliveries table.

### Integration Modules

Payload formatting is delegated to tiny integration helpers in `src/lib/webhooks/integrations/`:

- **Slack**: [`src/lib/webhooks/integrations/slack.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/integrations/slack.ts) formats blocks and mrkdwn text.
- **Telegram**: [`src/lib/webhooks/integrations/telegram.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/integrations/telegram.ts) constructs the JSON body expected by the Telegram Bot API.
- **Discord**: [`src/lib/webhooks/integrations/discord.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/integrations/discord.ts) structures embeds and content fields.
- **Custom**: [`src/lib/webhooks/integrations/custom.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/integrations/custom.ts) handles raw JSON templates with optional placeholder interpolation.

## Step-by-Step Implementation Guide

### 1. Define the Webhook Record

Create a webhook using the REST API at [`src/app/api/webhooks/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/webhooks/route.ts) or the CLI command defined in `bin/cli/commands/webhooks.mjs`:

```bash
omniroute webhooks add \
  --url https://hooks.slack.com/services/T000/B000/XXXXXXXX \
  --integration slack \
  --events chat.completion,quota.exhausted \
  --description "Notify Slack on completions & quota"

```

Alternatively, POST directly to `/api/webhooks`:

```http
POST /api/webhooks HTTP/1.1
Content-Type: application/json

{
  "url": "https://hooks.slack.com/services/T000/B000/XXXXXXXX",
  "integration": "slack",
  "events": ["chat.completion", "model.error"],
  "description": "Slack notifications for model activity"
}

```

### 2. Select Event Types

Available events are catalogued in [`src/lib/webhooks/eventDescriptions.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/eventDescriptions.ts). Common identifiers include:

- `chat.completion` – Fired after SSE response completion in [`src/open-sse/handlers/chatCore.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/open-sse/handlers/chatCore.ts).
- `quota.exhausted` – Triggered in [`src/lib/db/creditBalance.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/creditBalance.ts) after balance updates.
- `mcp.tool.invoked` – Emitted from [`src/open-sse/mcp-server/server.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/open-sse/mcp-server/server.ts) during tool execution.

Add these strings to the webhook's `events` array to subscribe.

### 3. Configure Custom Payloads (Optional)

For non-standard integrations, store a JSON template in the `customPayload` column (added by migration [`029_webhooks_custom_payload.sql`](https://github.com/diegosouzapw/OmniRoute/blob/main/029_webhooks_custom_payload.sql)). The dispatcher passes this template to `buildCustomPayload` in [`src/lib/webhooks/integrations/custom.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/integrations/custom.ts), which performs simple interpolation:

```typescript
// src/lib/webhooks/integrations/custom.ts
export function buildCustomPayload(event: WebhookEvent, template: any) {
  const json = JSON.stringify(template).replace("{{detail}}", event.detail);
  return JSON.parse(json);
}

```

Set `integration: "custom"` and include your template in the database record to use this feature.

### 4. Test the Webhook

Use the diagnostic endpoint defined in `src/app/api/webhooks/[id]/route.ts`:

```bash
curl -X POST https://your-omniroute-instance/api/webhooks/wh-00abc123/test

```

The CLI provides a convenience wrapper:

```bash
omniroute webhooks test wh-00abc123

```

This triggers an immediate dispatch with a static "test" payload, bypassing the event pipeline to verify connectivity and formatting.

## Integration-Specific Payload Examples

### Slack

The `buildSlackPayload` function in [`src/lib/webhooks/integrations/slack.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/integrations/slack.ts) constructs message blocks:

```typescript
// src/lib/webhooks/integrations/slack.ts
export function buildSlackPayload(event: WebhookEvent) {
  return {
    text: `*${event.type}* – ${event.summary}`,
    blocks: [{ 
      type: "section", 
      text: { type: "mrkdwn", text: event.detail } 
    }],
  };
}

```

### Telegram

Telegram integration formats the payload for the Bot API sendMessage endpoint:

```typescript
// src/lib/webhooks/integrations/telegram.ts
export function buildTelegramPayload(event: WebhookEvent) {
  return {
    text: `<b>${event.type}</b>\n${event.detail}`,
    parse_mode: "HTML"
  };
}

```

### Discord

Discord webhooks receive embed-compatible JSON:

```typescript
// src/lib/webhooks/integrations/discord.ts
export function buildDiscordPayload(event: WebhookEvent) {
  return {
    content: `Event: ${event.type}`,
    embeds: [{
      title: event.summary,
      description: event.detail,
      timestamp: new Date().toISOString()
    }]
  };
}

```

## Manual Event Dispatch

You can trigger webhooks programmatically from your own application logic by importing the dispatcher:

```typescript
import { deliverWebhook } from "./src/lib/webhookDispatcher";

await deliverWebhook({
  type: "chat.completion",
  summary: "User X completed a request",
  detail: "Model: anthropic-1.3b, tokens: 274/1024",
  payload: { /* optional additional data */ }
});

```

This is useful for custom instrumentation or extending OmniRoute with proprietary event types.

## Security and Failure Handling

### SSRF Protection

Every outbound request is validated by the SSRF guard in [`src/lib/webhookDispatcher.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhookDispatcher.ts). The implementation rejects private IP ranges and internal hostnames unless explicitly allowlisted. See [`tests/unit/api/webhooks/webhook-url-ssrf-guard.test.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/tests/unit/api/webhooks/webhook-url-ssrf-guard.test.ts) for the specific validation rules.

### Automatic Disable on Failure

A background service in [`src/lib/services/webhookFailureMonitor.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/services/webhookFailureMonitor.ts) runs every minute to check delivery outcomes. If a webhook exceeds the default threshold of **5 consecutive failures**, the monitor flips `enabled` to `false` in the database. Administrators can re-enable the webhook via the API or CLI after resolving the endpoint issue.

## Summary

- **Define webhooks** in SQLite using [`src/lib/db/webhooks.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/webhooks.ts) via the REST API (`/api/webhooks`) or CLI (`omniroute webhooks add`).
- **Select events** from the catalogue in [`src/lib/webhooks/eventDescriptions.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/eventDescriptions.ts) such as `chat.completion` or `quota.exhausted`.
- **Choose integrations** (Slack, Telegram, Discord, or custom) to automatically format payloads via modules in `src/lib/webhooks/integrations/`.
- **Test deliveries** using the `/api/webhooks/:id/test` endpoint or CLI test command.
- **Rely on built-in safeguards** including SSRF protection in [`src/lib/webhookDispatcher.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhookDispatcher.ts) and automatic disable logic in [`src/lib/services/webhookFailureMonitor.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/services/webhookFailureMonitor.ts).

## Frequently Asked Questions

### What events can I subscribe to in OmniRoute webhooks?

OmniRoute emits typed events throughout its request pipeline, including `chat.completion` (fired after model responses), `quota.exhausted` (triggered on balance updates), and `mcp.tool.invoked` (logged during MCP tool execution). The complete catalogue is maintained in [`src/lib/webhooks/eventDescriptions.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/eventDescriptions.ts) with stable identifiers and sample payloads.

### How does OmniRoute handle webhook failures and retries?

The `postWithRetry` function in [`src/lib/webhookDispatcher.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhookDispatcher.ts) implements exponential backoff for failed HTTP requests. Each attempt is logged to [`src/lib/db/webhookDeliveries.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/webhookDeliveries.ts). A background monitor ([`src/lib/services/webhookFailureMonitor.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/services/webhookFailureMonitor.ts)) counts consecutive failures and automatically disables the webhook after reaching the configurable threshold (default 5 errors).

### Can I use custom webhook endpoints outside of Slack, Telegram, or Discord?

Yes. Set the `integration` field to `"custom"` and provide a JSON template in the `customPayload` column (added by migration [`029_webhooks_custom_payload.sql`](https://github.com/diegosouzapw/OmniRoute/blob/main/029_webhooks_custom_payload.sql)). The dispatcher sends the raw JSON unchanged after optional placeholder interpolation via [`src/lib/webhooks/integrations/custom.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/webhooks/integrations/custom.ts).

### Where are webhook delivery logs stored in OmniRoute?

Delivery logs reside in the SQLite database table managed by [`src/lib/db/webhookDeliveries.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/webhookDeliveries.ts). Each record contains the webhook ID, timestamp, success status, HTTP response code, and error message, enabling audit trails and debugging of failed deliveries.