Reliable webhook delivery infrastructure for modern applications.
Release status: The latest published release is v1.1.0 — Operational Visibility (tag). The
mainbranch tracks stable development.
Built with Go and MongoDB, Relay accepts events over HTTP, persists them in MongoDB, and delivers them asynchronously to matching webhook endpoints with retries, delivery tracking, and replay. Ingestion and delivery are separate stages so the ingest path stays fast and predictable while the delivery engine applies backoff, concurrency limits, and recovery independently.
- Runtime: Go
- Persistence: MongoDB 7 (Go Driver v2)
- Delivery: Bounded workers, retries with backoff, per-endpoint state
- Operations: Delivery history, health checks, Prometheus & Grafana dashboards
- API: Fully documented OpenAPI (Swagger) specification
- SDKs: Official Node.js/TypeScript client
The fastest way to get Relay running locally is with Docker Compose:
git clone https://github.com/rajputomsingh/relay.git
cd relay
make docker-upSee docs/deployment.md for production configuration details.
git clone https://github.com/rajputomsingh/relay.git
cd relay
go mod downloadConfigure .env from .env.example (see Configuration), then:
go run ./cmd/relayValidate changes with the full gate:
go test ./...
go vet ./...
go build ./...
git diff --checkInstall the official SDK to integrate Relay into your Node.js or Next.js app:
npm install @rajputomsingh/relay-node
# or pnpm add @rajputomsingh/relay-nodeUsage:
import { Relay } from "@rajputomsingh/relay-node";
const relay = new Relay({ apiKey: "your-api-key" });
// Send an event with a guaranteed idempotency key
await relay.sendEvent(
{
source: "my-app",
event_type: "user.created",
data: { userId: 123 },
},
"idempotency-key-123",
);Each accepted event flows through five stages:
- Ingestion.
POST /v1/eventsauthenticates the caller, validates the JSON body, and enforcesIdempotency-Key. - Persistence. Events are written to MongoDB before any delivery begins. Each matching endpoint gets an independent delivery record.
- Matching and scheduling. Enabled endpoints are filtered by
sourceandevent_type. Matching deliveries enter the pending pool. - Bounded delivery workers. Workers claim deliveries with lease tokens, execute the outbound HTTP request, and record the attempt.
- Outcome and recovery. Attempts resolve to success, retry with backoff, or terminal failure. Expired leases are reclaimed; terminal failures are eligible for replay.
Relay provides at-least-once delivery. It does not guarantee exactly-once processing — a successful HTTP response plus a persisted attempt record cannot jointly guarantee a single downstream effect. Any downstream requiring single-effect semantics must key idempotency on the event id.
- Idempotency keys. The
Idempotency-Keyheader rejects duplicate submissions of the same logical event. It prevents duplicate ingestion; it does not prevent duplicate delivery attempts. - Retry classification. Relay retries
5xx, network errors, connection refusals, and timeouts.4xxother than408and429are terminal. - Worker crash. If a worker dies mid-attempt, its lease expires and the delivery is reclaimed. The retry after reclamation is logged as a new attempt. Downstreams may see a delivery more than once.
- Lease fencing. A stale worker attempting to record an outcome after its lease is reclaimed is rejected, so stale results cannot overwrite current state.
- Downstream idempotency. Because duplicate delivery is possible, downstream handlers must be idempotent.
Relay is configured entirely through environment variables, read at startup; validation failures abort the process.
| Variable | Required | Default | Notes |
|---|---|---|---|
APP_ENV |
No | development |
development / staging / production |
PORT |
No | 8080 |
Valid TCP port |
MONGODB_URI |
Yes | — | Must parse as a MongoDB URI |
MONGODB_DATABASE |
No | relay |
Non-empty |
RELAY_API_KEY |
Yes | — | Validated for minimum length (32 chars) |
RELAY_WEBHOOK_URL |
No | — | First-run bootstrap only |
RELAY_WEBHOOK_SIGNING_SECRET |
No | — | If set, at least 32 characters |
.env and files containing credentials must never be committed. In production, source these from a secrets manager.
All /v1/* routes require Authorization: Bearer <RELAY_API_KEY>.
Relay includes an interactive Swagger UI. When running the server locally, navigate to: 👉 http://localhost:8080/swagger/index.html
| Method | Endpoint | Auth | Purpose |
|---|---|---|---|
GET |
/healthz |
No | Liveness (process running) |
GET |
/readyz |
No | Readiness (MongoDB reachable) |
GET |
/metrics |
Yes | Prometheus metrics endpoint |
POST |
/v1/events |
Yes | Submit an event |
POST |
/v1/events/{id}/replay |
Yes | Replay an eligible failed event |
POST |
/v1/endpoints |
Yes | Register an endpoint |
GET |
/v1/endpoints |
Yes | List endpoints |
GET |
/v1/endpoints/{id} |
Yes | Retrieve one endpoint |
PATCH |
/v1/endpoints/{id} |
Yes | Update endpoint configuration |
DELETE |
/v1/endpoints/{id} |
Yes | Disable an endpoint (soft delete) |
GET |
/v1/events/{id}/deliveries |
Yes | Per-endpoint delivery status |
GET |
/v1/events/{id}/attempts |
Yes | Attempt history |
When an endpoint has a signing secret, Relay signs outbound requests with HMAC-SHA256 over the raw body and sends the hex digest in X-Relay-Signature.
Verify in Go
func verifyRelaySignature(rawBody []byte, header string, secret []byte) error {
if header == "" {
return errors.New("missing X-Relay-Signature")
}
mac := hmac.New(sha256.New, secret)
mac.Write(rawBody)
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(header), []byte(expected)) {
return errors.New("signature mismatch")
}
return nil
}Verify in Node.js
function verifyRelaySignature(rawBody, header, secret) {
if (!header) throw new Error("missing X-Relay-Signature");
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const a = Buffer.from(header, "utf8");
const b = Buffer.from(expected, "utf8");
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
throw new Error("signature mismatch");
}
}- Health.
/healthzreturns200while the process runs./readyzreturns200only when MongoDB responds to ping. - Metrics.
/metricsexposes Prometheus metrics for outbound delivery duration and delivery attempt outcomes. - Dashboards. A pre-provisioned Grafana dashboard and Prometheus config are included in
docker-compose.monitoring.yml. - Shutdown. On
SIGINT/SIGTERM, Relay stops accepting new requests, drains in-flight deliveries up to the shutdown deadline, then exits. - Retry policy. Backoff is
retryDelay(n) = 2s × 2^(n-1) + jitter(0–25%), capped per delay and bounded bymax_attempts.
Relay includes a benchmark harness for sustained load, concurrency, failure modes, outage recovery, and queue growth. Full methodology and raw results: docs/benchmarks.md.
| Measurement | Result |
|---|---|
| Workload | 5,000 deliveries |
| Highest tested concurrency | 32 |
| Highest reported throughput | 43.5 deliveries/s |
| Highest-concurrency drain | 115 s |
- Metrics currently query MongoDB on scrape. For very high-throughput setups, an in-memory metrics aggregator is recommended.
- No tenant isolation or per-tenant authorization (single-tenant instance per deployment).
- No web UI for event inspection, monitoring, or replay (API-only).
Completed (v1.0.0 & v1.1.0)
- Production-ready Dockerfile & Compose setup.
- Prometheus metrics instrumentation & Grafana dashboards.
- OpenAPI (Swagger) interactive documentation.
- Hardened CI/CD pipeline (CircleCI).
- Official Node.js/TypeScript SDK.
Planned (v1.2.0+)
- Tenant isolation and multi-tenant authorization.
- Web dashboard for event inspection and replay.
- Python and Go SDKs.
Unit tests (no infrastructure):
go test ./internal/config ./internal/security ./internal/handlers -count=1Integration tests (MongoDB required):
export MONGODB_TEST_URI="mongodb://localhost:27017"
go test ./internal/delivery -count=1Full validation gate (before proposing changes):
go test ./...
go vet ./...
go build ./...
git diff --checkLicensed under the Apache License 2.0. See LICENSE for details.

