omnihook 0.1.0

Generic webhook client with HMAC signing and platform-specific payload builders (Slack, Discord, Telegram)
Documentation

Crates.io Docs.rs License Build Status

Omnihook

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 built-in support for HMAC-SHA256 signing.

Features

  • Multi-Platform Support: Built-in builders for:
    • Slack: Blocks-based messages with mrkdwn.
    • Discord: Content-based messages with markdown.
    • Telegram: MarkdownV2 formatted messages with chat ID support.
    • Generic: Custom JSON payloads with template variables.
  • HMAC Signing: Secure your webhooks with HMAC-SHA256 signatures and timestamps.
  • Middleware Support: Built on top of reqwest-middleware for extensible HTTP client behavior.
  • Async/Await: Native async support for high-performance notification delivery.

Installation

Add this to your Cargo.toml:

[dependencies]
omnihook = "0.1.0"

Usage

Basic Example

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, Some("idempotency_key")).await?;

    Ok(())
}

Customizing HTTP Client (Middleware)

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(())
}

HMAC Signing & Security

omnihook supports automatic payload signing using HMAC-SHA256. When a secret is provided in the configuration, every request will include x-signature and x-timestamp headers.

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
    client.notify("Alert", "Something happened", &GenericWebhookPayloadBuilder::default(), Some("idempotency_key")).await?;

    Ok(())
}

Payload Builders

The library uses the WebhookPayloadBuilder trait to allow for easy extensibility:

Slack

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" } } ] }

Discord

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" }

Telegram

Handles required chat_id and MarkdownV2 escaping.

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": "MarkdownV2", ... }

Generic

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" }

License