Vahak is a durable webhook ingestion and delivery engine written in Go, compiled as a single static binary (CGO_ENABLED=0). It provides crash-tested at-least-once delivery across process crashes (kill -9) without external message brokers (no Redis, Kafka, or RabbitMQ). Persistence is handled via an embedded pure-Go SQLite engine (WAL mode) or PostgreSQL 16, and the administrative React UI is compiled directly into the binary via go:embed.
[ HTTP Ingest ]
│
▼
[ Ingest Channel ] ── (Bounded queue, capacity: 20,000)
│
▼
[ Batch Flusher ] ── Aggregates 50-500 requests into atomic multi-row transactions
│
▼
[ Storage Commit ] ── Atomically inserts `requests` AND `delivery_jobs` (status = 'pending')
│
├──────────────────────────────────────────────┐
▼ ▼
[ 200 OK to Client ] [ Delivery Worker Pool ]
(Only after durable commit in durable mode) │
├─► [ Goja JS VM (50ms watchdog) ]
│
▼
[ Circuit Breaker ]
│
┌─────────────┴─────────────┐
▼ (2xx OK) ▼ (5xx / Timeout / Circuit Open)
[ Batch Status Update ] [ Reschedule in DB with Backoff ]
(status = 'delivered') │
▼
[ Recovery Sweeper ]
(Runs on startup + 2s ticker, leased drain loop)
Vahak supports two acknowledgment modes via ACK_MODE:
ACK_MODE=durable(Default, Recommended): The HTTP handler enqueues to the batcher, blocks until the atomic batch transaction commits to durable storage, and only then acknowledges200 OKto the sender and dispatches to the delivery worker pool. Guarantees zero acknowledged-event loss across abrupt process crashes (kill -9).ACK_MODE=fast(Opt-in): Returns200 OKimmediately upon channel enqueue (<0.5ms). Dispatches delivery concurrently with background batch writing. Designed for high-volume non-critical telemetry where in-memory loss across an abrupt process kill is acceptable.
- Pre-Dispatch Commitment: Before an event is dispatched to downstream targets, both the payload (
requests) and its queue entry (delivery_jobs,status = 'pending') are committed to storage atomically. - Delivery Confirmation: Successful deliveries push the job ID to
deliveredCh, where a status worker periodically updatesdelivery_jobs SET status = 'delivered' WHERE id IN (...)to minimize transaction lock frequency. - Recovery Sweeper & Leased Drain Loop: On startup and every 2 seconds thereafter, a background sweeper claims pending/expired jobs in batches of 100 using atomic worker leases (
ClaimPendingJobswith a 15-second lease). It continuously loops until the backlog is fully drained. Any webhook in-flight during an abrupt crash is safely recovered after lease expiry and re-delivered upon restart. - Terminal State Machine: Deliveries that encounter repeated failures are retried with exponential backoff up to 5 attempts (
MaxAttempts = 5). Upon exhausting retries, the job transitions to a terminalfailedstate. - Retention Protection Invariant: Storage retention pruning (
PruneExpired) runs hourly to enforceRETENTION_DAYS, but strictly protects uncompleted deliveries viaWHERE NOT EXISTS (SELECT 1 FROM delivery_jobs j WHERE j.request_id = requests.id AND j.status NOT IN ('delivered', 'failed', 'dead')). Webhooks destined for temporarily offline targets are never pruned until completed or retry exhaustion. - For complete failure mode specifications, see FAILURE_MODES.md.
On receiving SIGINT or SIGTERM:
http.Server.Shutdown(ctx)stops accepting new connections and waits for active in-flight requests to complete.- Background context cancels to halt new ingestion tickers.
batchWorkerdrains remaining items iningestChand commits a final batch to disk.- Storage engine closes cleanly via
sync.WaitGroup.
Vahak abstracts persistence behind a unified Go interface (store.Storage), configurable via DB_ENGINE:
| Parameter | Embedded SQLite (DB_ENGINE=sqlite) |
PostgreSQL (DB_ENGINE=postgres) |
|---|---|---|
| Driver | modernc.org/sqlite (Pure Go, CGO_ENABLED=0) |
jackc/pgx/v5 (pgxpool, max_conns=200) |
| Concurrency Model | Multi-reader, single-writer (WAL mode) | Multi-connection pool with simple query protocol |
| Durability Pragma | PRAGMA synchronous(NORMAL) (or FULL via SQLITE_SYNCHRONOUS) |
PostgreSQL WAL defaults |
| Write Protocol | Multi-row batch parameterization | Multi-row batching / COPY protocol |
| Data Retention | Automated hourly pruning via RETENTION_DAYS |
Automated hourly pruning via RETENTION_DAYS |
PRAGMA busy_timeout = 5000;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA wal_autocheckpoint = 10000;
PRAGMA foreign_keys = ON;
PRAGMA cache_size = -64000; -- 64MB memory cache
PRAGMA temp_store = MEMORY;
PRAGMA mmap_size = 268435456; -- 256MB memory-mapped I/ONote on
synchronous=NORMAL: Under SQLite WAL mode,NORMALguarantees zero database corruption and zero data loss across process crashes (kill -9, SIGKILL, segfault, application panic). In the event of catastrophic physical power loss (unplugging hardware) or an operating system kernel crash, uncheckpointed WAL frames in the OS page cache can be lost. If physical power-loss protection is strictly required, setSQLITE_SYNCHRONOUS=FULL. See FAILURE_MODES.md for details.
Vahak is designed around two primary operational patterns:
Best for: Local development, CI testing, edge gateways, single-node deployments.
Runs directly on your host machine as a standalone single binary with zero external dependencies:
- Database: Embedded SQLite (
vahak.db) running with pure-Go (CGO_ENABLED=0) and WAL mode. - Setup:
# .env configuration DB_ENGINE=sqlite DB_PATH=vahak.db ACK_MODE=durable # or "fast" for maximum raw throughput API_KEY=your_secure_api_key ALLOW_LOCAL_TARGETS=true go run ./cmd/server/main.go
Best for: Production servers, cloud deployments (EC2, Droplets, Kubernetes).
Runs both Vahak and tuned PostgreSQL 16 containers in an isolated internal Docker network (vahak_default), bypassing host OS socket bottlenecks:
- Database: PostgreSQL 16 Alpine with connection pooling (
pgxpool,max_connections=250) and streamingCOPYprotocol micro-batching. - Setup:
# Start the complete stack docker compose up -d # View live logs docker compose logs -f vahak
⚠️ Note on Hybrid Setups (Local Host Binary + Docker Postgres): Running the host binary while pointingDB_URLto a containerized Postgres on Windows often encounters host-OS friction (IPv6[::1]resolution conflicts with native Windows services, and bash shell environment shadowing). Use Mode 1 for local development and Mode 2 for containerized/production environments.
Create a benchmark endpoint pointing to the local mock sink (http://sink:9090) to obtain an ENDPOINT_UUID:
curl -s -X POST http://localhost:8080/api/endpoints \
-H "X-API-Key: docker_test_key" \
-H "Content-Type: application/json" \
-d '{"name":"bench","target_url":"http://sink:9090"}'Example response: {"id":"b1c2d3e4-5678-90ab-cdef-1234567890ab", ...}
Benchmark from inside the Docker bridge network (vahak_default) to measure pure daemon throughput without host network proxy limits (substitute the returned id):
docker run --rm --network vahak_default williamyeh/hey \
-n 15000 -c 200 -m POST \
-d '{"event":"bench"}' \
-T "application/json" \
"http://vahak:8080/hooks/<ENDPOINT_UUID>"Evaluated with hey (-n 15000, 200 concurrent connections) across the supported deployment targets:
| Mode | Engine & Execution Environment | Measured Throughput | Avg Latency | P99 Latency | Durability Guarantee |
|---|---|---|---|---|---|
ACK_MODE=durable (Default) |
Embedded SQLite (WAL) — Host Native (Windows NVMe) | 2,200 – 3,400 RPS | 58.0 – 85.0 ms | 190.0 – 480.0 ms | Crash-Tested Zero Loss: Process-crash safe; committed to WAL before 200 OK (uncheckpointed frames subject to OS power loss without SQLITE_SYNCHRONOUS=FULL) |
ACK_MODE=durable |
PostgreSQL 16 — Internal Docker Network (db:5432) |
3,500 – 5,000 RPS | 39.6 – 58.0 ms | 190.0 – 340.0 ms | Crash-Tested Zero Loss: CopyFrom committed before 200 OK |
ACK_MODE=fast (Opt-in) |
Embedded SQLite (WAL) — Host Native (Windows NVMe) | 40,000 – 55,000 RPS | 3.2 – 4.5 ms | 23.0 – 35.0 ms | In-Memory ACK: Channel enqueue before commit |
Vahak includes an automated chaos test suite (tests/chaos/crash_test.go) executed on every CI build:
- Starts an HTTP sink recording unique event IDs (
X-Vahak-Event-ID). - Boots Vahak in
ACK_MODE=durableand floods concurrent webhooks. - Mid-load, abruptly executes
kill -9(SIGKILL) on the Vahak process at a randomized threshold. - Restarts Vahak with the same database, allowing atomic worker leases to expire.
- Verifies that 100% of acknowledged requests (
200 OK) are recovered by the startup sweeper and delivered to the sink at least once. - Repeats across multiple randomized crash-recovery iterations and logs cumulative duplicate delivery metrics (configurable via
CHAOS_ITERATIONS).
# Run the multi-iteration kill -9 chaos test locally
go test -v -timeout 180s ./tests/chaos/...- SSRF Mitigation: Custom dialer performs DNS resolution at dial time and blocks private subnets (RFC 1918), loopback, cloud instance metadata (
169.254.0.0/16), and IPv6 link-local/unique-local ranges (fe80::/10,fc00::/7). IPv4-mapped IPv6 addresses are normalized before validation, and HTTP redirects are strictly disabled to prevent SSRF redirect-hopping. Permitted only whenALLOW_LOCAL_TARGETS=true. - API Key Startup Enforcement: Refuses to boot if
API_KEYis empty or set to the defaultdocker_test_keywithout explicitALLOW_INSECURE_DEFAULT_KEY=true. - CPU Starvation Protection: User-defined JavaScript transformations running in the embedded
gojaruntime are wrapped with atime.AfterFuncwatchdog terminating execution after 50ms to prevent infinite loops (while(true)). - Circuit Breaker State Machine: Per-host failure tracking. 5 consecutive delivery failures trip the circuit into
Openstate. Requests during cooldown are rescheduled todelivery_jobswith exponential backoff without firing outbound network calls. - Bounded Retention: Background worker executes hourly pruning of records where
received_at < NOW() - INTERVAL 'RETENTION_DAYS', with an explicit invariant protecting pending/retrying jobs from deletion during prolonged downstream outages.
| Environment Variable | Description | Default |
|---|---|---|
PORT |
HTTP server listen port | 8080 |
ACK_MODE |
Ingestion acknowledgment mode: durable or fast |
durable |
DB_ENGINE |
Storage driver: sqlite or postgres |
sqlite |
DB_PATH |
Path to SQLite file (when DB_ENGINE=sqlite) |
vahak.db |
SQLITE_SYNCHRONOUS |
SQLite sync pragma: NORMAL (process-crash safe) or FULL (power-loss safe) |
NORMAL |
DB_URL |
PostgreSQL connection string | "" |
DB_POOL_URL |
PostgreSQL connection pool URL (falls back to DB_URL) |
"" |
API_KEY |
Secret token for administrative /api/* endpoints (Required) |
"" |
ALLOW_INSECURE_DEFAULT_KEY |
Allow using default test keys in local dev/testing | false |
ALLOW_LOCAL_TARGETS |
Permit delivery to private/loopback IPs | false |
RETENTION_DAYS |
Storage TTL in days for requests table | 7 |
# Unit & integration tests
go test -v ./...
# Race condition detection
go test -race ./...
# Chaos crash recovery verification
go test -v -timeout 180s ./tests/chaos/...The name Vahak (वाहक) is derived from Sanskrit, meaning Carrier, Bearer, or Messenger — historically referring to one who reliably delivers a message across distances without faltering.
Contributions, issues, and feature requests are welcome!
- Fork the repository
- Create your feature branch (
git checkout -b feat/your-feature) - Commit your changes (
git commit -m 'feat: add some feature') - Push to the branch (
git push origin feat/your-feature) - Open a Pull Request
Maintainer: Swastik Bose (VasuBhakt)
Distributed under the MIT License. See LICENSE.txt for details.