Documentation
WebhookLens is an open-source webhook infrastructure platform. Receive, inspect, replay, and forward webhooks with full visibility. Self-host for free or use the managed cloud.
-
Start the stack
docker compose up -d— launches PostgreSQL, ClickHouse, Redis, and WebhookLens. -
Open the dashboard
Navigate tohttp://localhost:3000. Sign up to create your tenant. -
Create an endpoint and send a test webhook
Create an endpoint in the dashboard, then:curl -X POST http://localhost:8080/wh/your-tenant/your-endpoint -H "Content-Type: application/json" -d '{"test": true}'
Requirements
Docker (recommended)
- Docker 20+ and Docker Compose v2+
- 1 CPU, 1 GB RAM minimum (2 CPU, 4 GB recommended)
Single binary
- Go 1.25+ (for building from source)
- PostgreSQL 16+
- ClickHouse 24+
- Redis 7+
- 1 CPU, 1 GB RAM minimum (2 CPU, 4 GB recommended)
Installation — Docker Compose
The recommended way to run WebhookLens. A single file brings up the entire stack: PostgreSQL, ClickHouse, Redis, and the WebhookLens server.
Download and start
# Download the community compose file
curl -fsSL https://raw.githubusercontent.com/webhooklens/webhooklens/main/deploy/docker-compose.community.yml \
-o docker-compose.yml
# Start the stack
docker compose up -d
The dashboard is available at http://localhost:3000 and the webhook proxy at http://localhost:8080.
Full docker-compose.community.yml
# WebhookLens Community Edition — Docker Compose
# Usage: docker compose -f docker-compose.community.yml up -d
# Dashboard: http://localhost:3000
# Proxy: http://localhost:8080
version: '3.8'
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: webhooklens
POSTGRES_PASSWORD: webhooklens
POSTGRES_DB: webhooklens
volumes:
- pg_data:/var/lib/postgresql/data
- ./migrations/postgres:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U webhooklens"]
interval: 5s
retries: 5
clickhouse:
image: clickhouse/clickhouse-server:24-alpine
volumes:
- ch_data:/var/lib/clickhouse
- ./migrations/clickhouse:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD", "clickhouse-client", "--query", "SELECT 1"]
interval: 5s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 5
webhooklens:
image: webhooklens/webhooklens:latest
ports:
- "3000:3000"
- "8080:8080"
environment:
WHL_DATABASE__POSTGRES_URL: postgres://webhooklens:webhooklens@postgres:5432/webhooklens?sslmode=disable
WHL_DATABASE__CLICKHOUSE_URL: clickhouse://clickhouse:9000/webhooklens
WHL_DATABASE__REDIS_URL: redis://redis:6379
WHL_AUTH__JWT_SECRET: change-me-in-production-use-64-chars-minimum
depends_on:
postgres: { condition: service_healthy }
clickhouse: { condition: service_healthy }
redis: { condition: service_healthy }
restart: unless-stopped
volumes:
pg_data:
ch_data:
redis_data:
WHL_AUTH__JWT_SECRET to a random string of at least 32 characters in production.
Installation — Single Binary
Download a pre-built binary or compile from source. Requires PostgreSQL, ClickHouse, and Redis running separately.
Pre-built binary
# Download the latest release
curl -fsSL https://github.com/webhooklens/webhooklens/releases/latest/download/webhooklens-linux-amd64 \
-o webhooklens
chmod +x webhooklens
# Run with a config file
./webhooklens --config config.yaml
Build from source
git clone https://github.com/webhooklens/webhooklens.git
cd webhooklens
go build -o webhooklens ./cmd/server
./webhooklens --config config.yaml
Configuration
WebhookLens can be configured via a YAML config file or environment variables. Environment variables use the WHL_ prefix with double underscores for nesting.
Server
| Config Key | Env Var | Default | Description |
|---|---|---|---|
server.proxy_port | WHL_SERVER__PROXY_PORT | 8080 | Port for webhook ingestion |
server.api_port | WHL_SERVER__API_PORT | 3000 | Port for dashboard and API |
server.debug | WHL_SERVER__DEBUG | false | Enable debug logging |
server.cloud_domain | WHL_SERVER__CLOUD_DOMAIN | "" | Cloud mode domain (multi-tenant SaaS) |
Database
| Config Key | Env Var | Default | Description |
|---|---|---|---|
database.postgres_url | WHL_DATABASE__POSTGRES_URL | — | PostgreSQL connection string |
database.clickhouse_url | WHL_DATABASE__CLICKHOUSE_URL | — | ClickHouse connection string |
database.redis_url | WHL_DATABASE__REDIS_URL | — | Redis connection string |
Authentication
| Config Key | Env Var | Default | Description |
|---|---|---|---|
auth.jwt_secret | WHL_AUTH__JWT_SECRET | — | JWT signing secret (min 32 characters) |
auth.github_client_id | WHL_AUTH__GITHUB_CLIENT_ID | "" | GitHub OAuth Client ID |
auth.github_secret | WHL_AUTH__GITHUB_SECRET | "" | GitHub OAuth Secret |
retention.events_days | WHL_RETENTION__EVENTS_DAYS | 30 | Auto-delete events after N days |
Rate Limiting
| Config Key | Env Var | Default | Description |
|---|---|---|---|
rate_limit.api_per_tenant | WHL_RATE_LIMIT__API_PER_TENANT | 100 | API requests per minute per tenant |
rate_limit.auth_per_ip | WHL_RATE_LIMIT__AUTH_PER_IP | 10 | Auth requests per minute per IP |
Stripe Billing
Required only for cloud/SaaS mode with paid plans.
| Config Key | Env Var | Default | Description |
|---|---|---|---|
stripe.api_key | WHL_STRIPE__API_KEY | "" | Stripe secret key |
stripe.price_id | WHL_STRIPE__PRICE_ID | "" | Stripe price ID for the subscription |
stripe.webhook_secret | WHL_STRIPE__WEBHOOK_SECRET | "" | Stripe webhook signing secret |
stripe.base_url | WHL_STRIPE__BASE_URL | "" | Base URL for Stripe checkout redirects |
Configure email delivery for password resets and notifications. Uses Resend.
| Config Key | Env Var | Default | Description |
|---|---|---|---|
email.resend_api_key | WHL_EMAIL__RESEND_API_KEY | "" | Resend API key |
email.from_address | WHL_EMAIL__FROM_ADDRESS | noreply@... | Sender address for outbound emails |
Example config.yaml
server:
proxy_port: 8080
api_port: 3000
debug: false
database:
postgres_url: "postgres://user:pass@localhost:5432/webhooklens?sslmode=disable"
clickhouse_url: "clickhouse://localhost:9000/webhooklens"
redis_url: "redis://localhost:6379"
auth:
jwt_secret: "your-random-secret-at-least-32-characters"
retention:
events_days: 30
rate_limit:
api_per_tenant: 100
auth_per_ip: 10
Webhook Proxy
WebhookLens acts as a reverse proxy for your webhooks. Point your providers to the proxy URL and WebhookLens receives, logs, validates, and forwards every request to your actual endpoint.
Receiving Webhooks
The proxy URL follows this pattern:
POST https://your-domain:8080/wh/{tenant-slug}/{endpoint-slug}
When a webhook arrives, WebhookLens:
- Receives the full HTTP request (method, headers, body, query params)
- Stores the event in ClickHouse for analytics and PostgreSQL for inspection
- Validates the signature (if a provider is configured on the endpoint)
- Applies transforms (if any are configured)
- Forwards the request to the configured target URL
- Records the response status, latency, and any errors
Retry policy
Failed deliveries (5xx responses or timeouts) are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 1 second |
| 2nd retry | 5 seconds |
| 3rd retry | 30 seconds |
| 4th retry | 5 minutes |
| 5th retry | 30 minutes |
After 5 failed retries the event is marked as failed. You can manually replay it from the dashboard or API at any time.
Signature Validation
When you configure a provider on an endpoint, WebhookLens automatically validates the webhook signature using the provider's signing algorithm. If validation fails, the event is flagged but still stored and forwarded (unless you enable strict mode).
Example: Stripe signature verification
# When creating the endpoint, set the provider and signing secret
curl -X POST http://localhost:3000/api/endpoints \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"slug": "stripe-payments",
"target_url": "https://api.example.com/webhooks/stripe",
"provider": "stripe",
"signing_secret": "whsec_..."
}'
Supported Providers
WebhookLens supports automatic signature validation for the following providers:
| Provider | Signature Header | Algorithm |
|---|---|---|
| Stripe | Stripe-Signature | HMAC-SHA256 (timestamp + payload) |
| GitHub | X-Hub-Signature-256 | HMAC-SHA256 |
| Shopify | X-Shopify-Hmac-SHA256 | HMAC-SHA256 (Base64) |
| Slack | X-Slack-Signature | HMAC-SHA256 (timestamp + body) |
| Twilio | X-Twilio-Signature | HMAC-SHA1 |
| SendGrid | X-Twilio-Email-Event-Webhook-Signature | ECDSA |
| Paddle | Paddle-Signature | HMAC-SHA256 (timestamp + payload) |
| Linear | Linear-Signature | HMAC-SHA256 |
| Svix | svix-signature | HMAC-SHA256 (msg-id + timestamp + body) |
| Clerk | svix-signature | Svix-based HMAC-SHA256 |
| Intercom | X-Hub-Signature | HMAC-SHA1 |
| Typeform | Typeform-Signature | HMAC-SHA256 |
| Generic HMAC | Configurable | HMAC-SHA256 |
Dashboard
Events & Inspector
The events page shows all incoming webhooks in real-time with auto-refresh. Each row shows:
- Status — forwarded (green), failed (red), replayed (blue), timeout (yellow)
- Provider — auto-detected from headers (Stripe, GitHub, Shopify, etc.)
- Endpoint — which endpoint received the webhook
- Latency — round-trip time to forward and receive a response
- Timestamp — relative time ("2s ago") with full ISO timestamp on hover
Click any event to open the inspector:
- Request tab: headers, body (syntax-highlighted JSON/XML), query parameters
- Response tab: target response status, headers, body
- Timeline tab: full delivery timeline including retries
- Replay button: re-send the exact same request to the target
Endpoints
Endpoints are the core abstraction. Each endpoint has:
- A unique slug that forms part of the proxy URL
- A target URL where webhooks are forwarded
- An optional provider for signature validation
- Optional transforms for payload manipulation
- Optional routing rules for multi-destination fan-out
Analytics
The analytics dashboard provides real-time metrics powered by ClickHouse:
- Volume — total events over time (1h, 24h, 7d, 30d)
- Success rate — percentage of 2xx responses
- Latency — P50, P95, P99 percentiles
- Per-provider breakdown — volume and success by provider
- Per-endpoint breakdown — volume and success by endpoint
Alerts
Configure alerts to be notified when something goes wrong:
- Failure threshold — alert when error rate exceeds N% over a time window
- Latency threshold — alert when P95 latency exceeds N ms
- No events — alert when no events received for N minutes
Notifications can be sent to Slack (via incoming webhook) or to a custom webhook URL.
# Create an alert
curl -X POST http://localhost:3000/api/alerts \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "High failure rate",
"type": "failure_rate",
"threshold": 10,
"window_minutes": 5,
"channel": "slack",
"slack_webhook_url": "https://hooks.slack.com/services/..."
}'
Transforms & Routing
Payload Transforms
Transforms let you modify webhook payloads before they are forwarded to the target. This is useful for enriching data, removing sensitive fields, or adapting payloads to your API format.
Transform types
| Type | Description | Example |
|---|---|---|
set_field | Set or overwrite a JSON field | Add source: "stripe" to the body |
remove_field | Remove a JSON field | Strip data.object.metadata |
rename_field | Rename a JSON field | Rename id to external_id |
add_header | Add a request header | Add X-Source: webhooklens |
remove_header | Remove a request header | Strip X-Internal-Token |
Example: add a field and a header
curl -X PATCH http://localhost:3000/api/endpoints/EP_ID \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"transforms": [
{"type": "set_field", "path": "meta.source", "value": "stripe"},
{"type": "add_header", "key": "X-Source", "value": "webhooklens"},
{"type": "remove_field", "path": "data.object.metadata.internal"}
]
}'
Dry-run test
Test your transforms without actually forwarding:
curl -X POST http://localhost:3000/api/transforms/test \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"transforms": [
{"type": "set_field", "path": "enriched", "value": true}
],
"payload": {"event": "payment.completed", "amount": 4999}
}'
# Response:
# {"event": "payment.completed", "amount": 4999, "enriched": true}
Smart Routing
Route a single webhook to multiple destinations based on conditions. This enables fan-out, conditional processing, and fallback targets.
Routing conditions
| Condition | Description |
|---|---|
all | Always forward to this target (fan-out) |
on_success | Forward only if the primary target returned 2xx |
on_failure | Forward only if the primary target returned non-2xx or timed out |
Example: fan-out to 3 targets
curl -X PATCH http://localhost:3000/api/endpoints/EP_ID \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"target_url": "https://primary.example.com/webhook",
"routes": [
{"url": "https://analytics.example.com/ingest", "condition": "all"},
{"url": "https://backup.example.com/webhook", "condition": "all"},
{"url": "https://alerts.example.com/failure", "condition": "on_failure"}
]
}'
API Reference
All API endpoints are served on the API port (default :3000). Authenticated endpoints require a Bearer token obtained from login.
Authentication
Create a new account and tenant.
curl -X POST http://localhost:3000/api/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "you@example.com",
"password": "securepassword",
"tenant_name": "my-project"
}'
# Response: {"token": "eyJhbG...", "tenant": {"id": "...", "slug": "my-project"}}
Authenticate and receive a JWT token.
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "securepassword"}'
# Response: {"token": "eyJhbG..."}
Endpoints API
List all endpoints for the current tenant.
curl http://localhost:3000/api/endpoints \
-H "Authorization: Bearer YOUR_TOKEN"
# Response: [{"id": "...", "slug": "stripe-prod", "target_url": "https://...", ...}]
Create a new endpoint.
curl -X POST http://localhost:3000/api/endpoints \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"slug": "stripe-prod",
"target_url": "https://api.example.com/webhooks/stripe",
"provider": "stripe",
"signing_secret": "whsec_..."
}'
Update an endpoint (target URL, provider, transforms, routes).
curl -X PATCH http://localhost:3000/api/endpoints/EP_ID \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"target_url": "https://new-target.example.com/hook"}'
Events API
List events with optional filters. Supports pagination.
# List recent events
curl "http://localhost:3000/api/events?limit=50&status=failed" \
-H "Authorization: Bearer YOUR_TOKEN"
# Filter by endpoint
curl "http://localhost:3000/api/events?endpoint_id=EP_ID&limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"
Replay an event — re-send the original request to the target.
curl -X POST http://localhost:3000/api/events/EVT_ID/replay \
-H "Authorization: Bearer YOUR_TOKEN"
# Response: {"status": "replayed", "response_code": 200, "latency_ms": 142}
Analytics API
Get aggregate analytics for the tenant.
curl "http://localhost:3000/api/analytics/overview?period=24h" \
-H "Authorization: Bearer YOUR_TOKEN"
# Response:
# {
# "total_events": 12847,
# "success_rate": 99.2,
# "p50_ms": 45,
# "p95_ms": 210,
# "p99_ms": 890,
# "by_provider": [{"provider": "stripe", "count": 8420}, ...],
# "by_status": {"forwarded": 12744, "failed": 62, "replayed": 41}
# }
Billing API
Available in cloud mode only.
Get current usage and plan quota.
curl http://localhost:3000/api/billing/quota \
-H "Authorization: Bearer YOUR_TOKEN"
# Response: {"plan": "starter", "events_used": 12847, "events_limit": 50000, ...}
Create a Stripe Checkout session for upgrading.
curl -X POST http://localhost:3000/api/billing/checkout \
-H "Authorization: Bearer YOUR_TOKEN"
# Response: {"checkout_url": "https://checkout.stripe.com/..."}
Create a Stripe Billing Portal session for managing subscriptions.
curl -X POST http://localhost:3000/api/billing/portal \
-H "Authorization: Bearer YOUR_TOKEN"
# Response: {"portal_url": "https://billing.stripe.com/..."}
Dry-run a set of transforms against a sample payload.
CLI Reference
Installation
go install github.com/webhooklens/webhooklens/cmd/webhooklens-cli@latest
Commands
Login
# Authenticate with a WebhookLens server
webhooklens-cli login --server https://app.webhooklens.cloud
# For self-hosted
webhooklens-cli login --server http://localhost:3000
Listen (local tunnel)
# Forward webhooks to a local development server
webhooklens-cli listen --port 4000
# Specify an endpoint
webhooklens-cli listen --port 4000 --endpoint stripe-prod
Events
# List recent events
webhooklens-cli events list
# Show event details
webhooklens-cli events show EVT_ID
# Replay an event to a different URL
webhooklens-cli events replay EVT_ID --to http://localhost:4000/webhook
| Command | Description |
|---|---|
webhooklens-cli login | Authenticate with a server |
webhooklens-cli listen | Forward webhooks to a local port (dev tunnel) |
webhooklens-cli events list | List recent events |
webhooklens-cli events show <id> | Show full event details (headers, body, response) |
webhooklens-cli events replay <id> | Replay an event, optionally to a different URL |
Self-Hosting Guide
Production Deployment
For production, we recommend running behind Caddy for automatic HTTPS and using the production Docker Compose file.
- Set a strong
WHL_AUTH__JWT_SECRET(64+ random characters) - Use dedicated PostgreSQL, ClickHouse, and Redis instances (or managed services)
- Enable TLS via Caddy or your load balancer
- Set up automated backups
- Configure rate limiting appropriate for your traffic
TLS with Caddy
Caddy automatically provisions and renews Let's Encrypt TLS certificates.
Caddyfile
# /etc/caddy/Caddyfile
webhooks.example.com {
reverse_proxy localhost:3000
}
proxy.example.com {
reverse_proxy localhost:8080
}
# Start Caddy
sudo caddy run --config /etc/caddy/Caddyfile
Backup & Restore
Back up PostgreSQL (primary data) regularly. ClickHouse data can be regenerated from events if needed.
Backup script
#!/bin/bash
# backup-webhooklens.sh
BACKUP_DIR="/backups/webhooklens"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# PostgreSQL backup
docker exec webhooklens-postgres-1 \
pg_dump -U webhooklens webhooklens | \
gzip > "${BACKUP_DIR}/pg_${TIMESTAMP}.sql.gz"
# Keep last 30 backups
ls -t "${BACKUP_DIR}"/pg_*.sql.gz | tail -n +31 | xargs rm -f
echo "Backup completed: pg_${TIMESTAMP}.sql.gz"
Cron schedule
# Run daily at 2 AM
0 2 * * * /opt/webhooklens/backup-webhooklens.sh >> /var/log/webhooklens-backup.log 2>&1
Restore
# Restore from backup
gunzip -c "/backups/webhooklens/pg_20260411_020000.sql.gz" | \
docker exec -i webhooklens-postgres-1 \
psql -U webhooklens webhooklens
Monitoring
WebhookLens exposes a health endpoint for uptime checks and monitoring:
curl http://localhost:3000/healthz
# Response (healthy):
# {"status": "ok", "postgres": "ok", "clickhouse": "ok", "redis": "ok"}
# Response (degraded):
# {"status": "degraded", "postgres": "ok", "clickhouse": "error", "redis": "ok"}
Use this endpoint with your monitoring system (UptimeRobot, Datadog, Prometheus blackbox exporter, etc.) to get alerted when any component is unhealthy.
/metrics on the API port. Use these for detailed monitoring of throughput, latency, and error rates in Grafana.
Need help? Open an issue on GitHub or reach out at support@webhooklens.cloud.