Reliable webhook delivery service. Retry, circuit breaker, dead letter queue, HMAC signing — the works.
Your webhook endpoint went down at 2am. Your events didn't get lost. That's the point.
| Document | Description |
|---|---|
| 🚀 QUICKSTART.md | Start here! Get running in 2 minutes |
| 📘 EXAMPLES.md | API usage examples & code snippets |
| 🔧 TROUBLESHOOTING.md | Common issues & solutions |
| 📁 PROJECT_STRUCTURE.md | File organization & architecture |
| ⚙️ GIT_SETUP.md | Git configuration after cloning |
- At-least-once delivery — events survive process restarts, they're in PostgreSQL
- Exponential backoff — with jitter, per-endpoint config, respects
Retry-After - Circuit breaker — per endpoint, Redis-backed, shared across workers
- Dead letter queue — browse, replay single or bulk, with idempotency guards
- HMAC-SHA256 signing — Stripe-style
t=timestamp,v1=signature, replay attack prevention - Full audit log — every delivery attempt with status, latency, response body
| Component | Why |
|---|---|
| FastAPI | Async REST API |
| SQLAlchemy 2.0 async | PostgreSQL ORM |
| arq | Async Redis task queue (not Celery — overkill) |
| Redis | Queue backend + circuit breaker state |
| httpx | Async HTTP delivery client |
| Alembic | Database migrations |
| PostgreSQL 16 | Durable event storage |
One-command setup after cloning:
git clone <repo-url>
cd WebhookRelay
make setupPowerShell users (рекомендуется):
# Автоматическая установка с проверками и retry
.\setup.ps1Что произойдет:
- ✓ Проверка Docker daemon
- ✓ Загрузка базовых образов (с автоматическими повторами при ошибках)
- ✓ Сборка образов приложения
- ✓ Запуск PostgreSQL, Redis, API и 2x Workers
- ✓ Автоматический запуск миграций БД
- ✓ Проверка здоровья всех сервисов
Endpoints:
- API:
http://localhost:8742 - Docs:
http://localhost:8742/docs - DB:
localhost:54320(postgres/postgres) - Redis:
localhost:63790
Useful commands:
make logs
make health
make down
make clean
make testTroubleshooting:
- Если Docker выдает TLS ошибки, запустите:
docker system prune -f && make setup - Проверьте что Docker Desktop запущен:
docker info - Логи конкретного сервиса:
docker compose logs -f api
┌─────────┐ ┌───────────┐ ┌─────────────┐
│ Client │────▶│ FastAPI │────▶│ PostgreSQL │
└─────────┘ │ (API) │ │ (events, │
└─────┬──────┘ │ attempts, │
│ │ DLQ) │
▼ └──────────────┘
┌───────────┐ ▲
│ Redis │ │
│ (arq + │ ┌──────┴──────┐
│ circuit │────▶│ arq Worker │──▶ POST endpoint
│ breaker) │ │ (delivery) │
└───────────┘ └─────────────┘
POST /api/v1/endpoints Register webhook endpoint
GET /api/v1/endpoints List endpoints
GET /api/v1/endpoints/{id} Get endpoint
PATCH /api/v1/endpoints/{id} Update endpoint
DELETE /api/v1/endpoints/{id} Deactivate endpoint
GET /api/v1/endpoints/{id}/stats Delivery stats + circuit state
POST /api/v1/endpoints/{id}/rotate-secret Rotate signing secret
POST /api/v1/events Ingest event for delivery
GET /api/v1/events/{id} Event status + attempts
GET /api/v1/events/{id}/attempts Delivery attempt log
GET /api/v1/dlq Browse DLQ
GET /api/v1/dlq/{id} DLQ entry details
POST /api/v1/dlq/{id}/replay Replay single event
POST /api/v1/dlq/replay/bulk Bulk replay with filters
DELETE /api/v1/dlq/{id} Discard entry
GET /api/v1/health Health check
GET /api/v1/stats Global delivery stats
POST /api/v1/inbound/verify Test HMAC verification
Register an endpoint:
curl -X POST http://localhost:8742/api/v1/endpoints \
-H "Content-Type: application/json" \
-d '{"name": "My Service", "url": "https://httpbin.org/post"}'Send an event:
curl -X POST http://localhost:8742/api/v1/events \
-H "Content-Type: application/json" \
-d '{
"endpoint_id": "<id-from-above>",
"event_type": "order.created",
"payload": {"order_id": 42, "total": 99.99}
}'Check delivery:
curl http://localhost:8742/api/v1/events/<event-id>- Event created → status
pending - arq worker picks it up → status
delivering - HMAC-sign payload, POST to endpoint
- Success (2xx) → status
delivered, circuit breaker records success - Retryable failure (5xx, timeout) → status
failed, exponential backoff, next attempt scheduled - Permanent failure (4xx) → straight to DLQ
- All retries exhausted → DLQ
- 410 Gone → endpoint auto-deactivated
Per-endpoint, Redis-backed. State shared across all workers.
CLOSED ──(5 failures in 60s)──▶ OPEN ──(300s timeout)──▶ HALF_OPEN
▲ │
└────────────(success)──────────────────────────────────────┘
│
(failure)
│
▼ OPEN
Every outgoing delivery includes:
X-Webhook-Signature: t=1614556800,v1=abc123def456...
X-Webhook-Event-ID: <uuid>
X-Webhook-Event-Type: order.created
X-Webhook-Timestamp: 1614556800
Receivers verify: HMAC-SHA256(timestamp + "." + body, signing_secret).
Timestamp tolerance: 5 minutes (replay attack prevention).
src/webhook_relay/
├── api/v1/ REST endpoints
├── models/ SQLAlchemy ORM
├── schemas/ Pydantic v2
├── repositories/ Data access layer
├── services/ Business logic
├── worker/ arq task queue
├── config.py Settings via env vars
├── database.py Async SQLAlchemy engine
├── main.py FastAPI app factory
└── middleware.py Request ID + timing
| Service | Port |
|---|---|
| API | 8742 |
| PostgreSQL | 54320 |
| Redis | 63790 |
Non-default ports to avoid conflicts with local services.
MIT Webhooks that actually arrive. Retry, DLQ, HMAC verification, replay — production-grade delivery in one service.