chipa-webhooks
A fast, non-blocking webhook dispatch crate with a handlebars-powered template engine. Built for high-frequency trading platforms where the dispatch path must never block the main thread.
Features
- Non-blocking dispatch —
sendpushes to akanalchannel and returns immediately. All HTTP work happens in a background task. - Handlebars templates — register named templates, render any
serde::Serializetype into them. - TypeId-based matcher — register a rule closure per type,
sendresolves the right template automatically. - Runtime template mutations — add, update, or remove templates while the dispatcher is running.
- Fan-out to multiple destinations — one send reaches Discord, Telegram, Slack, Ntfy, and any other platform concurrently.
- Platform hints — pass per-message metadata like embed color or title via
WithHintswithout polluting your data types. - Graceful shutdown —
shutdown().awaitdrains the queue before returning. - Flush barrier —
flush().awaitwaits for all queued jobs to complete without closing the channel. - Isolated errors — a failing destination never affects others. Errors are reported via an
on_errorcallback.
Supported Platforms
| Platform | Type | Notes |
|---|---|---|
| Discord | ✅ | Webhook URL, embed color, embed title, username |
| Telegram | ✅ | Bot token + chat ID, MarkdownV2 / HTML / Plain, silent, disable preview |
| Slack | ✅ | Incoming webhook URL, username, icon emoji |
| Ntfy | ✅ | Self-hosted or ntfy.sh, title, priority, tags |
| Generic HTTP | ✅ | Plain JSON POST to any URL, configurable body key |
Platforms planned
| Platform | Notes |
|---|---|
| Microsoft Teams | Adaptive Cards, high demand in finance orgs |
| Mattermost | Slack-compatible payload |
| Pushover | Mobile push, popular with individual traders |
| PagerDuty | On-call alerting, severity levels map well to trade events |
| OpsGenie | Popular PagerDuty alternative |
| Lark / Feishu | Large user base in Asian markets |
| DingTalk | Same target market as Lark |
| WeChat Work | Enterprise WeChat, relevant for CN trading firms |
| Email (SMTP) | Via lettre, useful for low-frequency critical alerts |
Installation
[]
= "0.1"
Quick Start
use ;
use Serialize;
async
Send Methods
| Method | Template source | Hints |
|---|---|---|
send(&event) |
Matcher (TypeId lookup) | — |
send_with_hints(&event, hints) |
Matcher (TypeId lookup) | ✅ |
send_with_template("name", &event) |
Explicit | — |
send_with_template_and_hints("name", &event, hints) |
Explicit | ✅ |
All four methods are async and return immediately after queuing — the HTTP request happens in the background.
Platform Hints
WithHints is a builder that attaches per-message metadata. Hints are stripped from the template data before rendering so they never appear in the message text.
new
// Discord
.d_color // embed color (u32 RGB)
.d_title // embed title
// Telegram
.tg_silent // disable notification sound
.tg_disable_preview // disable link preview
// Slack
.slack_username // override bot name for this message
.slack_emoji // override icon emoji for this message
// Ntfy
.ntfy_title // notification title
.ntfy_priority // 1 (min) to 5 (max)
.ntfy_tags // comma-separated, additive with struct-level tags
Discord color reference
| Meaning | Hex |
|---|---|
| Buy / Win / OK | 0x2ecc71 |
| Sell / Loss / Error | 0xe74c3c |
| Hold / Neutral | 0x95a5a6 |
| Warning | 0xe67e22 |
| Info | 0x3498db |
Multi-Platform Fan-out
One dispatcher fans out to all destinations concurrently. Each destination is fully isolated — a timeout or error on one never delays or affects the others.
use ;
use Serialize;
async
Runtime Template Mutations
Templates can be changed while the dispatcher is running. Always call flush().await before mutating to ensure all queued sends complete first — sends are fire-and-forget into a channel, so without a flush, in-flight jobs may render against the mutated template.
// Phase 1 — send with original templates
dispatcher.send_with_template.await.unwrap;
// Wait for all queued HTTP requests to finish before mutating
dispatcher.flush.await.unwrap;
// Phase 2 — mutate, then send
dispatcher.update_template.unwrap;
dispatcher.register_template.unwrap;
dispatcher.remove_template;
dispatcher.send_with_template.await.unwrap;
dispatcher.send_with_template.await.unwrap;
Graceful Shutdown
// Closes the channel, drains every queued job to completion, then returns.
// No messages are lost.
dispatcher.shutdown.await;
Custom Platform
Implement the Platform trait to add any HTTP-based platform:
use Platform;
use ;
// Use it like any built-in platform
new;
Telegram Setup
- Create a bot via @BotFather and copy the token.
- Start a conversation with the bot (send
/start). - Retrieve your chat ID:
Look forhttps://api.telegram.org/bot<TOKEN>/getUpdates"chat": { "id": 123456789 }in the response.
use ;
new
Ntfy Setup
Works with the public ntfy.sh server or any self-hosted instance.
use ;
// Public ntfy.sh
new
// Self-hosted
new
Architecture
Caller
│
│ send(&event) ← async, returns after channel push
▼
kanal bounded channel (capacity: 1024 by default)
│
▼
Background task (tokio::spawn)
├── TemplateEngine::render() ← handlebars, Arc<RwLock> shared with caller
└── fan-out via join_all
├── Platform::build_payload() + reqwest POST → Discord
├── Platform::build_payload() + reqwest POST → Telegram
├── Platform::build_payload() + reqwest POST → Slack
├── Platform::build_payload() + reqwest POST → Ntfy
└── Platform::build_payload() + reqwest POST → ...
- The
TemplateEngineis shared between the caller and the background task viaArc<RwLock>. The caller holds a write lock only duringregister/update/removecalls. The background task holds a read lock only duringrender. - A
Flushsentinel job is enqueued byflush(). When the background task reaches it, all prior jobs are guaranteed to have completed their HTTP fan-outs.
Environment Variables (for tests)
DISCORD_WEBHOOK=https://discord.com/api/webhooks/<id>/<token>
WEBHOOK_SITE_URL=https://webhook.site/<uuid>
Dependencies
| Crate | Role |
|---|---|
kanal |
High-performance async channel |
tokio |
Async runtime |
reqwest |
HTTP client (rustls, no OpenSSL) |
handlebars |
Template engine |
serde + serde_json |
Serialization |
futures |
join_all for concurrent fan-out |
thiserror |
Error type derivation |
tracing |
Structured logging |