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.

Quick Start Get running in under 60 seconds with Docker Compose. No credit card required for the cloud trial.
  1. Start the stack
    docker compose up -d — launches PostgreSQL, ClickHouse, Redis, and WebhookLens.
  2. Open the dashboard
    Navigate to http://localhost:3000. Sign up to create your tenant.
  3. 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)

Single binary


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:
Important Change 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 KeyEnv VarDefaultDescription
server.proxy_portWHL_SERVER__PROXY_PORT8080Port for webhook ingestion
server.api_portWHL_SERVER__API_PORT3000Port for dashboard and API
server.debugWHL_SERVER__DEBUGfalseEnable debug logging
server.cloud_domainWHL_SERVER__CLOUD_DOMAIN""Cloud mode domain (multi-tenant SaaS)

Database

Config KeyEnv VarDefaultDescription
database.postgres_urlWHL_DATABASE__POSTGRES_URLPostgreSQL connection string
database.clickhouse_urlWHL_DATABASE__CLICKHOUSE_URLClickHouse connection string
database.redis_urlWHL_DATABASE__REDIS_URLRedis connection string

Authentication

Config KeyEnv VarDefaultDescription
auth.jwt_secretWHL_AUTH__JWT_SECRETJWT signing secret (min 32 characters)
auth.github_client_idWHL_AUTH__GITHUB_CLIENT_ID""GitHub OAuth Client ID
auth.github_secretWHL_AUTH__GITHUB_SECRET""GitHub OAuth Secret
retention.events_daysWHL_RETENTION__EVENTS_DAYS30Auto-delete events after N days

Rate Limiting

Config KeyEnv VarDefaultDescription
rate_limit.api_per_tenantWHL_RATE_LIMIT__API_PER_TENANT100API requests per minute per tenant
rate_limit.auth_per_ipWHL_RATE_LIMIT__AUTH_PER_IP10Auth requests per minute per IP

Stripe Billing

Required only for cloud/SaaS mode with paid plans.

Config KeyEnv VarDefaultDescription
stripe.api_keyWHL_STRIPE__API_KEY""Stripe secret key
stripe.price_idWHL_STRIPE__PRICE_ID""Stripe price ID for the subscription
stripe.webhook_secretWHL_STRIPE__WEBHOOK_SECRET""Stripe webhook signing secret
stripe.base_urlWHL_STRIPE__BASE_URL""Base URL for Stripe checkout redirects

Email

Configure email delivery for password resets and notifications. Uses Resend.

Config KeyEnv VarDefaultDescription
email.resend_api_keyWHL_EMAIL__RESEND_API_KEY""Resend API key
email.from_addressWHL_EMAIL__FROM_ADDRESSnoreply@...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:

  1. Receives the full HTTP request (method, headers, body, query params)
  2. Stores the event in ClickHouse for analytics and PostgreSQL for inspection
  3. Validates the signature (if a provider is configured on the endpoint)
  4. Applies transforms (if any are configured)
  5. Forwards the request to the configured target URL
  6. Records the response status, latency, and any errors

Retry policy

Failed deliveries (5xx responses or timeouts) are retried with exponential backoff:

AttemptDelay
1st retry1 second
2nd retry5 seconds
3rd retry30 seconds
4th retry5 minutes
5th retry30 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:

ProviderSignature HeaderAlgorithm
StripeStripe-SignatureHMAC-SHA256 (timestamp + payload)
GitHubX-Hub-Signature-256HMAC-SHA256
ShopifyX-Shopify-Hmac-SHA256HMAC-SHA256 (Base64)
SlackX-Slack-SignatureHMAC-SHA256 (timestamp + body)
TwilioX-Twilio-SignatureHMAC-SHA1
SendGridX-Twilio-Email-Event-Webhook-SignatureECDSA
PaddlePaddle-SignatureHMAC-SHA256 (timestamp + payload)
LinearLinear-SignatureHMAC-SHA256
Svixsvix-signatureHMAC-SHA256 (msg-id + timestamp + body)
Clerksvix-signatureSvix-based HMAC-SHA256
IntercomX-Hub-SignatureHMAC-SHA1
TypeformTypeform-SignatureHMAC-SHA256
Generic HMACConfigurableHMAC-SHA256

Dashboard

Events & Inspector

The events page shows all incoming webhooks in real-time with auto-refresh. Each row shows:

Click any event to open the inspector:

Endpoints

Endpoints are the core abstraction. Each endpoint has:

Analytics

The analytics dashboard provides real-time metrics powered by ClickHouse:

Alerts

Configure alerts to be notified when something goes wrong:

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

TypeDescriptionExample
set_fieldSet or overwrite a JSON fieldAdd source: "stripe" to the body
remove_fieldRemove a JSON fieldStrip data.object.metadata
rename_fieldRename a JSON fieldRename id to external_id
add_headerAdd a request headerAdd X-Source: webhooklens
remove_headerRemove a request headerStrip 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

ConditionDescription
allAlways forward to this target (fan-out)
on_successForward only if the primary target returned 2xx
on_failureForward 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

POST /api/auth/signup

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"}}
POST /api/auth/login

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

GET /api/endpoints

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://...", ...}]
POST /api/endpoints

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_..."
  }'
PATCH /api/endpoints/:id

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

GET /api/events

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"
POST /api/events/:id/replay

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 /api/analytics/overview

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 /api/billing/quota

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, ...}
POST /api/billing/checkout

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/..."}
POST /api/billing/portal

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/..."}
POST /api/transforms/test

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
CommandDescription
webhooklens-cli loginAuthenticate with a server
webhooklens-cli listenForward webhooks to a local port (dev tunnel)
webhooklens-cli events listList 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.

Checklist Before going to production, make sure to:

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.

Prometheus metrics WebhookLens also exposes Prometheus-compatible metrics at /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.