Webhook Dispatcher — Local Dev Setup
Status: populated Owner: Platform Engineering Last updated: 2026-04-18 Companion: DEPLOYMENT_TOPOLOGY
1. Prerequisites
| Tool | Minimum Version |
|---|---|
| Node.js | 20 LTS |
| pnpm | 8.x |
| Docker + Docker Compose | 24.x |
NATS CLI (nats) | 0.1.x |
| Flyway CLI | 10.x |
2. Local Stack (Docker Compose)
# docker-compose.dev.yml (excerpt relevant to webhook-dispatcher)
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: ghasi
POSTGRES_USER: postgres
POSTGRES_PASSWORD: dev_password
ports: ["5432:5432"]
nats:
image: nats:2.10-alpine
command: ["-js", "-m", "8222"]
ports: ["4222:4222", "8222:8222"]
# Simple echo server to act as a mock webhook endpoint
webhook-echo:
image: mendhak/http-https-echo:31
ports: ["8080:8080"]
Start: docker compose -f docker-compose.dev.yml up -d
3. Database Setup
flyway -url=jdbc:postgresql://localhost:5432/ghasi \
-user=postgres -password=dev_password \
-locations=filesystem:./migrations \
migrate
4. NATS Stream Setup
# Create WEBHOOK_DISPATCH stream
nats stream add WEBHOOK_DISPATCH \
--subjects="webhook.dispatch" \
--retention=limits \
--max-age=24h \
--server=nats://localhost:4222
# Create durable consumer
nats consumer add WEBHOOK_DISPATCH webhook-dispatcher \
--ack=explicit \
--deliver=all \
--max-pending=20 \
--server=nats://localhost:4222
5. Environment Variables
Create .env.local:
NATS_URL=nats://localhost:4222
DATABASE_URL=postgresql://postgres:dev_password@localhost:5432/ghasi
DB_POOL_MIN=2
DB_POOL_MAX=10
KMS_KEY_ID=local-dev-key # Uses local mock KMS (see §6)
HTTP_DELIVERY_TIMEOUT_MS=5000
RETRY_WORKER_INTERVAL_MS=10000
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
LOG_LEVEL=debug
PORT=3000
6. Local KMS Mock
For local development, use a symmetric key stored in a local env variable instead of KMS:
# .env.local (dev only)
KMS_LOCAL_DEV_KEY=dev-symmetric-key-32bytes-padding # 32 chars for AES-256
KMS_MODE=local # Service reads KMS_MODE to switch between real KMS and local AES
Never use this in staging or production.
7. Register a Test Webhook
# First create a test account (or use existing seed data)
curl -X POST http://localhost:3000/v1/webhooks \
-H "Content-Type: application/json" \
-H "X-Account-Id: d4e5f6a7-b8c9-0123-def0-234567890123" \
-d '{
"url": "http://localhost:8080/webhook",
"secret": "test-secret-16chars-minimum",
"description": "Local echo webhook",
"events": ["DLR_DELIVERED", "DLR_FAILED"]
}'
8. Publish a Test Dispatch Event
nats pub webhook.dispatch '{
"eventId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"schemaVersion": "1.0",
"accountId": "d4e5f6a7-b8c9-0123-def0-234567890123",
"messageId": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"dlrStatus": "DELIVERED",
"to": "+441234567890",
"operatorId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"occurredAt": "2026-04-18T10:23:46Z"
}' --server=nats://localhost:4222
Check the webhook-echo server logs for received request, and verify:
psql postgresql://postgres:dev_password@localhost:5432/ghasi \
-c "SELECT attempt_id, status, http_status_code, attempted_at FROM hook.delivery_attempts ORDER BY created_at DESC LIMIT 5;"
9. Run Tests
pnpm install
pnpm test # unit tests
pnpm test:int # integration tests (requires Docker)
pnpm test:cov # coverage report