Crates.io Docs.rs License Build Status
omnihook is a flexible, type-safe Rust library for sending webhook notifications to various platforms. It provides platform-specific payload builders for Slack, Discord, Telegram, and generic endpoints, with optional HMAC-SHA256 signing.
- Multi-Platform Support: Built-in builders for:
- Slack: Blocks-based messages with mrkdwn.
- Discord: Content-based messages with markdown.
- Telegram: HTML-formatted messages with markdown support and chat ID.
- Generic: Custom JSON payloads with optional HMAC signing and idempotency keys.
- Middleware Support: Built on top of
reqwest-middlewarefor extensible HTTP client behavior. - Async/Await: Native async support for high-performance notification delivery.
- Ready-to-use Examples: Check the examples/ directory for platform-specific implementations.
Add this to your Cargo.toml:
[dependencies] omnihook = "0.1.1"
use omnihook::{WebhookClient, WebhookConfig, SlackPayloadBuilder}; use url::Url; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let url = Url::parse("https://hooks.slack.com/services/T000/B000/XXXX")?; // 1. Configure the webhook let config = WebhookConfig::new(url); // 2. Build client using default HTTP settings let client = config.build()?; // 3. Send notification let builder = SlackPayloadBuilder::default(); client.notify("System Alert", "Database is down!", &builder).await?; Ok(()) }
Since omnihook uses reqwest-middleware, you can add retries, logging, or caching. To do this, provide your own Arc<ClientWithMiddleware> to WebhookClient::new:
use std::sync::Arc; use reqwest_middleware::ClientBuilder; use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff}; use omnihook::{WebhookClient, WebhookConfig}; use url::Url; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // 1. Setup retry policy let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); let http_client = ClientBuilder::new(reqwest::Client::new()) .with(RetryTransientMiddleware::new_with_policy(retry_policy)) .build(); // 2. Wrap in Arc and pass to client let config = WebhookConfig::new(Url::parse("https://...")?); let client = WebhookClient::new(config, Arc::new(http_client))?; Ok(()) }
omnihook supports automatic payload signing using HMAC-SHA256 for Generic webhooks. When a secret is provided in the configuration, every request will include x-signature and x-timestamp headers. Use this when sending notifications to a custom endpoint where you wish to verify the payload's integrity.
use omnihook::{WebhookClient, WebhookConfig, GenericWebhookPayloadBuilder}; use url::Url; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let config = WebhookConfig::new(Url::parse("https://your-api.com/webhook")?) .with_secret("top-secret-key"); // Enables automatic signing let client = config.build()?; // This call will now automatically sign the payload and include an idempotency key client.notify_with_key("Alert", "Something happened", &GenericWebhookPayloadBuilder::default(), Some("idempotency_key")).await?; Ok(()) }
You can pass an optional idempotency key using notify_with_key. This will add an Idempotency-Key header to the request, which is useful for preventing duplicate processing on custom generic endpoints.
client.notify_with_key("Alert", "Something happened", &builder, Some("your-unique-key")).await?;
The library uses the WebhookPayloadBuilder trait to allow for easy extensibility:
Uses Slack's Block Kit for structured messages.
use omnihook::{SlackPayloadBuilder, WebhookPayloadBuilder}; let builder = SlackPayloadBuilder::default(); let payload = builder.build_payload("Alert", "Something happened"); // Returns: { "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "Alert\n\nSomething happened" } } ] }
Simple markdown-enabled content messages.
use omnihook::{DiscordPayloadBuilder, WebhookPayloadBuilder}; let builder = DiscordPayloadBuilder::default(); let payload = builder.build_payload("Alert", "Something happened"); // Returns: { "content": "Alert\n\nSomething happened" }
Handles required chat_id and markdown-to-HTML conversion.
use omnihook::{TelegramPayloadBuilder, WebhookPayloadBuilder}; let builder = TelegramPayloadBuilder { chat_id: "123456789".to_string(), disable_web_preview: true, }; let payload = builder.build_payload("Alert", "Something happened"); // Returns: { "chat_id": "123456789", "text": "Alert\n\nSomething happened", "parse_mode": "HTML", ... }
Producing a standard high-level JSON object.
use omnihook::{GenericWebhookPayloadBuilder, WebhookPayloadBuilder}; let builder = GenericWebhookPayloadBuilder::default(); let payload = builder.build_payload("Alert", "Something happened"); // Returns: { "title": "Alert", "body": "Something happened" }
You can run the examples provided in the examples/ directory using cargo run --example <name>. Most examples look for environment variables for configuration:
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..." cargo run --example slack
export DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/..." cargo run --example discord
export TELEGRAM_BOT_TOKEN="123456:ABC..." export TELEGRAM_CHAT_ID="123456789" cargo run --example telegram
export GENERIC_WEBHOOK_URL="https://api.yourserver.com/webhook" cargo run --example generic
- MIT license (http://opensource.org/licenses/MIT)