- Subscription CRUD (API & UI) with secret and event type filtering.
- Webhook ingestion endpoint with HMAC signature verification.
- Asynchronous delivery worker with exponential backoff retries.
- Scheduled webhook delivery with recurrence (none, daily, weekly, monthly).
- Delivery attempt logging, analytics, and retention
- Redis-backed subscription caching (supports Upstash, Redis Cloud, etc.).
- Minimal UI for managing, testing, and analyzing subscriptions.
- Fully containerized with Docker and Docker Compose.
- Cloud-ready (Google Cloud Run, Fly.io, Railway, etc.).
- Health check endpoint (
/healthz
) for monitoring. - Dead Letter Queue for failed deliveries (manual retry via UI).
Prerequisites:
- Docker installed
- Docker Compose installed
- Turso CLI (optional, for local SQLite or Turso Cloud setup)
-
Clone the repository:
git clone https://github.com/KrishKoria/Webhook-Delivery-Service.git cd Webhook-Delivery-Service
-
Copy and edit the example environment file:
cp .env.example .env
-
Configure your database settings in the
.env
file. Refer to the "Environment Variables" section below for details.- For
TURSO_DATABASE_URL
andTURSO_AUTH_TOKEN
, see the "Local Database Setup" section below for guidance on SQLite (local) or Turso Cloud. - For
REDIS_URL
:- Local Docker Development: This is now optional. If
REDIS_URL
is not set or is empty in your.env
file, the application will default to using the Redis service defined indocker-compose.yml
(redis://redis:6379/0
). - External Redis (e.g., Upstash): Set
REDIS_URL
in your.env
file to connect to your cloud Redis provider. See the "Redis Caching" section.
- Local Docker Development: This is now optional. If
- For
-
-
Build and start the service (includes Redis):
docker-compose up --build
This command will also start a local Redis container.
-
Access the app:
- Open http://localhost:8080 (redirects to
/ui/subscriptions
).
- Open http://localhost:8080 (redirects to
The API documentation, generated from the OpenAPI specification, is available on Bump.sh:
View API Documentation on Bump.sh
The application uses the following environment variables for configuration:
TURSO_DATABASE_URL
: The connection URL for your Turso database (orfile:local.db
for local SQLite).TURSO_AUTH_TOKEN
: The authentication token for your Turso database (can be empty for local SQLite).REDIS_URL
: (Optional for local Docker setup) The connection URL for your Redis instance.- If running with
docker-compose
and this variable is empty or not set in.env
, it defaults to the internal Docker Redis service (redis://redis:6379/0
). - For external Redis providers (e.g., Upstash), set this to your provider's URL (e.g.,
rediss://:<password>@<host>:6379
).
- If running with
PORT
: (Optional) The port on which the HTTP server will listen. Defaults to8080
.
- Framework: Go + Gin for HTTP API and UI (fast, minimal, robust).
- Database: Turso (production, serverless SQLite) or SQLite (local/dev).
- Async Task/Queue: In-process Go worker, DB-backed queue for reliability.
- Scheduled Worker: Separate Go worker for scheduled webhooks, enqueues delivery tasks at the scheduled time.
- Retry Strategy: Exponential backoff (10s, 30s, 1m, 5m, 15m), max 5 attempts.
- Caching: Redis (Upstash/Redis Cloud) for subscription details.
- Containerization: Docker, orchestrated with Docker Compose.
- Cloud Ready: Deployable to Google Cloud Run, Fly.io, Railway, etc.
- Dead Letter Queue: Failed deliveries after max retries are moved to a DLQ for manual review/retry.
- Observability: Health check endpoint, structured logging
- The application uses Redis for subscription caching.
- Local Docker Development:
docker-compose.yml
includes a Redis service. IfREDIS_URL
is not set or is empty in your.env
file, the application will automatically connect to this local Redis service (redis://redis:6379/0
). - External Redis (Upstash, Redis Cloud, etc.):
- To use an external Redis provider, set the
REDIS_URL
in your.env
file (e.g.,rediss://:<password>@<host>:6379
for Upstash). This will override the local Docker Redis configuration. - No separate local Redis container is needed if you are using a cloud provider.
- To use an external Redis provider, set the
While Turso Cloud is the recommended method for a more robust development experience (see below), you can use a local SQLite file for quick local testing, especially when running with Docker.
When you run the application using docker-compose up
, you can configure it to use a local SQLite database file. This file will be created inside the Docker container.
-
Configure your
.env
file: Make sure your.env
file (copied from.env.example
) has the following settings:TURSO_DATABASE_URL=file:local.db TURSO_AUTH_TOKEN= # ... other variables like REDIS_URL, PORT ...
TURSO_DATABASE_URL=file:local.db
: This tells the application to use/create a SQLite database file namedlocal.db
.TURSO_AUTH_TOKEN=
: This must be empty for local SQLite file usage.
-
How it works with Docker:
- When you run
docker-compose up --build
, the application inside the Docker container will uselocal.db
. - The database migrations will be applied to this
local.db
file within the container. - Important: By default, this
local.db
file exists only within the Docker container's filesystem. If you rundocker-compose down
(which removes the container), thislocal.db
file and all its data will be lost.
- When you run
-
(Optional) Persisting
local.db
with Docker Volumes: If you need thelocal.db
data to persist across container restarts ordocker-compose down/up
cycles, you would need to modify yourdocker-compose.yml
to map a volume to wherelocal.db
is stored within the container (e.g.,/app/local.db
if yourWORKDIR
is/app
). This is a more advanced Docker setup. Example snippet fordocker-compose.yml
:services: app: # ... other app configurations ... volumes: - ./data/sqlite:/app/data # Assuming local.db is created at /app/data/local.db # or adjust path as needed. # Ensure TURSO_DATABASE_URL=file:/app/data/local.db
Note: If you use a volume, ensure the path in
TURSO_DATABASE_URL
matches the path inside the container where the volume is mounted, e.g.,TURSO_DATABASE_URL=file:/app/data/local.db
.
Limitations of local SQLite file with Docker (without persistent volumes):
- Data is ephemeral and lost when the container is removed.
- Accessing and inspecting the
local.db
file directly from your host machine is more complex as it resides inside the container.
For these reasons, for any development beyond very quick, isolated tests, using Turso Cloud is strongly recommended.
-
Create a Turso account: Sign up at Turso.
-
Install the Turso CLI and create a database: Follow the Turso documentation to install their CLI and create a new database.
-
Get your database URL and auth token: The Turso CLI will provide you with a database URL (e.g.,
libsql://your-db-name-username.turso.io
) and you can generate an auth token. -
Set these in your
.env
file:TURSO_DATABASE_URL=<your_turso_database_url_from_cli> TURSO_AUTH_TOKEN=<your_turso_auth_token_from_cli> # ... other variables ...
Important Note on Database URLs: The database URL provided to the application (whether local or remote) must start with a valid scheme like
file:
,libsql:
, orhttps:
. If you use just a filename (likelocal.db
) without thefile:
prefix forTURSO_DATABASE_URL
, it will likely not work correctly with the database driver. Always use the correct scheme!
- subscriptions:
id
(PK, UUID),target_url
,secret
,event_types
,created_at
,updated_at
- delivery_tasks:
id
(PK, UUID),subscription_id
(FK),payload
,status
,created_at
,last_attempt_at
,attempt_count
- delivery_logs:
id
(PK, UUID),delivery_task_id
(FK),subscription_id
(FK),target_url
,timestamp
,attempt_number
,outcome
,http_status
,error_details
- scheduled_webhooks:
id
(PK, UUID),subscription_id
(FK),payload
,scheduled_for
,recurrence
,status
,created_at
,updated_at
- dead_letter_tasks:
id
(PK, UUID),original_task_id
,subscription_id
,payload
,failed_at
,reason
- Indexes:
Onsubscription_id
,delivery_task_id
,scheduled_for
, andnext_attempt_at
for efficient lookups and scheduling.
curl -X POST http://localhost:8080/subscriptions \
-H "Content-Type: application/json" \
-d '{"target_url":"https://webhook.site/your-url","secret":"mysecret","event_types":"order.created,user.updated"}'
curl http://localhost:8080/subscriptions
curl -X PUT http://localhost:8080/subscriptions/<id> \
-H "Content-Type: application/json" \
-d '{"target_url":"https://webhook.site/your-new-url","secret":"newsecret","event_types":"order.updated,user.deleted"}'
curl -X DELETE http://localhost:8080/subscriptions/<id>
curl -X POST http://localhost:8080/ingest/<subscription_id> \
-H "Content-Type: application/json" \
-H "X-Event-Type: order.created" \
-H "X-Hub-Signature-256: sha256=<hmac>" \
-d '{"event":"test"}'
- Go to the subscription's "Schedule New" action in the UI.
- Fill out the form (payload, time, recurrence).
- The scheduled webhook will be delivered at the specified time and logged.
- Uses Redis for subscription caching (Upstash, Redis Cloud, etc.).
- Set
REDIS_URL
in your.env
(e.g.,rediss://:<password>@<host>:6379
for Upstash). - No local Redis container is needed if using a cloud provider.
- Compute: 1x small container (Cloud Run, Fly.io, Railway, etc.) ≈ $0/month (free tier)
- Database: Turso (free tier) or SQLite (local) ≈ $0/month
- Redis: Upstash free tier ≈ $0/month
- Bandwidth: Minimal, well within free tier limits
- Total: ≈ $0/month on free tier, with headroom for moderate traffic
- All secrets and sensitive data are managed via environment variables.
- The service is stateless and horizontally scalable.
- Turso is used for production DB; SQLite for local/dev.
- Delivery attempts are idempotent and safe to retry.
- Redis is optional for local/dev, required for production caching.
- Dead Letter Queue is enabled for failed deliveries.
- Scheduled webhooks are enqueued as delivery tasks at the scheduled time.
- Google Cloud Run:
See the README section "Deploying to Google Cloud Run" for step-by-step instructions. - Upstash Redis:
Use the providedREDIS_URL
from Upstash in your.env
. - Docker Hub:
Build and push your image:docker build -t <your-dockerhub-username>/webhook-delivery-service:latest . docker push <your-dockerhub-username>/webhook-delivery-service:latest
- Scheduled Webhooks:
Schedule one-time or recurring webhooks (daily, weekly, monthly) via the UI.
Scheduled worker enqueues delivery tasks at the correct time. - Dead Letter Queue:
Failed deliveries after max retries are moved to a DLQ for manual review and redelivery. - Manual Redelivery:
UI allows retrying failed deliveries from the DLQ. - Health Check:
/healthz
endpoint for monitoring and orchestration.
You can deploy this service to Google Cloud Run in just a few steps:
Replace <YOUR_PROJECT_ID>
and <YOUR_IMAGE_NAME>
with your values.
# Authenticate Docker with Google
gcloud auth configure-docker
# Build your image
docker build -t gcr.io/<YOUR_PROJECT_ID>/<YOUR_IMAGE_NAME>:latest .
# Push to Google Container Registry
docker push gcr.io/<YOUR_PROJECT_ID>/<YOUR_IMAGE_NAME>:latest
gcloud run deploy webhook-delivery-service \
--image gcr.io/<YOUR_PROJECT_ID>/<YOUR_IMAGE_NAME>:latest \
--platform managed \
--region <YOUR_REGION> \
--allow-unauthenticated \
--set-env-vars TURSO_DATABASE_URL=<YOUR_DATABASE_URL>,TURSO_AUTH_TOKEN=<YOUR_AUTH_TOKEN>,REDIS_URL=<YOUR_REDIS_URL>
After deployment, you will receive a URL for your service. You can access the UI and API at
https://<your-cloud-run-url>/ui/subscriptions