NitroHook: a self-hosted webhook gateway in Go

NitroHook is a self-hosted webhook gateway that receives, transforms, and fans out webhook deliveries to multiple destinations — HTTP endpoints, Slack, email (SMTP), Twilio SMS, and sandboxed JavaScript handlers. Built with Go, PostgreSQL, and Redis Streams.

Sources list

Features

  • Multi-destination fan-out — route one incoming webhook to any combination of HTTP, Slack, SMTP, Twilio, and JavaScript actions.
  • Sandboxed JS transforms — attach per-source or per-action scripts (Goja runtime, ES5+) to mutate payloads, drop events, or filter which actions fire. 500ms timeout, 64KB limit.
  • Retry with exponential backoff + jitter — failed deliveries retry automatically with per-attempt tracking.
  • HMAC-SHA256 signing — outbound deliveries are signed so downstream consumers can verify authenticity.
  • Record mode — capture incoming webhooks without dispatching, then replay on demand.
  • Prometheus metrics — ingest latency, dispatch duration, pending queue depth, and retry backlog exposed on /metrics.

Transform script editor Monaco-powered script editor with inline testing against recorded payloads.

Architecture

Two cooperating processes backed by PostgreSQL and Redis:

  1. API server — receives incoming webhooks at POST /webhooks/:sourceSlug, persists the payload + headers, and publishes a delivery ID to a Redis Stream. Also serves the web UI, REST API, and Prometheus metrics.
  2. Fan-out worker — pool of consumers reads from the Redis Stream consumer group, resolves the source’s active actions, runs transform scripts, and dispatches through a pluggable dispatcher registry. Failed attempts schedule retries; a background poller sweeps retryable attempts and pending deliveries as a catch-up mechanism.

A delivery is marked completed only when every action succeeds or exhausts its retries.

Actions per source Per-source action list — toggle active/inactive inline, add webhooks/Slack/SMTP/Twilio/JS handlers.

Events list Event log — every incoming webhook with its status, idempotency key, and receive time.

Event detail with attempts Drill into an event to see headers, payload, and per-attempt HTTP status for every fan-out action.

Tech Stack

  • Language: Go 1.24
  • HTTP framework: Gin
  • Database: PostgreSQL 18 (pgx driver, golang-migrate for schema management)
  • Queue: Redis 8 Streams (consumer groups for competing workers)
  • JS engine: Goja (ES5+ sandbox)
  • Metrics: Prometheus client
  • Packaging: Docker, Docker Compose, Helm chart for Kubernetes
  • CI/CD: GitHub Actions → GHCR
×