Case · Backend · Rust
hook-relay: webhook delivery
A webhook delivery service in Rust: it accepts an event, signs it with HMAC-SHA256, sends it to the receiver and does not lose it. On failure it retries with exponential backoff and jitter, after the attempt limit it parks the delivery in a dead-letter state where it can be requeued by hand. The receiver address is checked for SSRF after DNS: every resolved address must be public. Idempotent by Idempotency-Key, state in SQLite.
- Rust
- axum
- tokio
- SQLite
- HMAC-SHA256
- SSRF
- proptest
- musl
- distroless
- Docker
- GitHub Actions
01 · How it looks
Screenshots


02 · About
What it is and why
The task: deliver webhooks so that an event is not lost, the receiver can verify authenticity, and the service itself cannot be used to reach an internal network.
The solution: Rust (axum + tokio + SQLite). The body is signed with HMAC-SHA256 over the string "t.body" using the receiver's secret; on failure delivery retries with exponential backoff and jitter and after the limit goes to dead-letter. The receiver address is resolved through DNS and every resulting address must be public.
Limitations
At-least-once delivery: the receiver must be idempotent (X-Relay-Delivery-Id).
Receiver secrets are stored in the database in plain text and never returned in API lists.
The receiver response body is not read or stored.
The project runs on a small VPS with demo data: no redundancy or backups.
What is inside
- SSRF: loopback, private, link-local, CGNAT, documentation, ULA, IPv4-mapped, NAT64 and 6to4 with an embedded private address are blocked; checked at registration and on every delivery
- Retries: network errors, 5xx, 408 and 429; other 4xx are a final refusal; manual requeue from dead-letter
- Idempotency by Idempotency-Key: a repeat returns the earlier delivery and does not send the event again
- After a crash restart in-flight deliveries return to the queue; graceful shutdown waits up to 15 s
- 20 tests; proptest: the signature fails after changing any byte, any private IPv4 and its IPv4-mapped form are blocked
- Result: a 14.4 MB image, 3.5 MiB idle and 5.5 MiB after 200 events with real delivery to a receiver
03 · How to run
Run it locally
git clone <repository on request> hook-relay
cd hook-relay
DATA_DIR=./data cargo run --release
# http://localhost:811304 · Code
The repository is private
The source is in a private repository. Message me on Telegram and I will open access.
Request the code