How to Implement Webhooks in OmniRoute: Complete Developer Guide

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. This module exports the Webhook type that defines the schema:

// 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, which records every attempt with timestamps, success flags, and response metadata.

Dispatcher Engine

The core delivery logic resides in src/lib/webhookDispatcher.ts. The deliverWebhook function selects enabled hooks matching the event type, builds service-specific payloads, and executes safe HTTP POST requests:

// 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/:

Step-by-Step Implementation Guide

1. Define the Webhook Record

Create a webhook using the REST API at src/app/api/webhooks/route.ts or the CLI command defined in bin/cli/commands/webhooks.mjs:

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:

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. Common identifiers include:

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). The dispatcher passes this template to buildCustomPayload in src/lib/webhooks/integrations/custom.ts, which performs simple interpolation:

// 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:

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

The CLI provides a convenience wrapper:

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 constructs message blocks:

// 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:

// 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:

// 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:

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. The implementation rejects private IP ranges and internal hostnames unless explicitly allowlisted. See 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 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

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 with stable identifiers and sample payloads.

How does OmniRoute handle webhook failures and retries?

The postWithRetry function in src/lib/webhookDispatcher.ts implements exponential backoff for failed HTTP requests. Each attempt is logged to src/lib/db/webhookDeliveries.ts. A background monitor (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). The dispatcher sends the raw JSON unchanged after optional placeholder interpolation via 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. Each record contains the webhook ID, timestamp, success status, HTTP response code, and error message, enabling audit trails and debugging of failed deliveries.

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 →