A production-grade Webhook Delivery Infrastructure with AI-Powered Failure Intelligence — built with Node.js, Redis, BullMQ, PostgreSQL & Claude AI
HookRelay is a webhook delivery infrastructure service — the same kind of system that powers Stripe, Razorpay, and GitHub's webhook notifications.
You register a URL. An event fires. HookRelay guarantees your URL gets notified — with automatic retries, exponential backoff, HMAC signature verification, and a full delivery log.
Think of it as a reliable postman that never gives up until the message is delivered.
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ API Server │──────▶│ BullMQ Queue │──────▶│ Worker Process │
│ (Express.js) │ │ (Redis-backed) │ │ (Job Processor)│
└─────────────────┘ └──────────────────┘ └────────┬────────┘
│ │
│ ▼
▼ ┌─────────────────┐
┌─────────────────┐ │ Target URL │
│ PostgreSQL │◀────────────────────────────────│ (HTTP POST) │
│ (Logs + Data) │ delivery logs └─────────────────┘
└─────────────────┘
Two separate processes:
server.js— handles all incoming API requestsworker.js— processes the delivery queue independently
- 🔐 JWT Auth with API key + secret key per user
- 🎯 Webhook Registration — register URLs with specific event type subscriptions
- ⚡ Async Event Triggering — fires jobs into queue, returns 200 instantly (never blocks sender)
- 🔁 Retry with Exponential Backoff — 3s → 9s → 27s → 81s → 243s before marking DEAD
- 💀 Dead Letter Queue — failed jobs stored separately for inspection
- 🔒 HMAC-SHA256 Signature — every delivery signed with
X-HookRelay-Signatureheader - 📋 Delivery Logs — full history of every attempt with status, response code, error
- 🚦 Rate Limiting — 100 events/min per API key (Redis-backed)
- 📊 Dashboard API — stats endpoint for frontend consumption
- 🧩 Toggle Webhooks — enable/disable without deleting
- 🤖 AI Failure Analyst — Claude AI reads your delivery logs and explains WHY webhooks are failing in plain English
- 🧠 AI Payload Validator — paste any payload and AI tells you if it's well-structured, suggests improvements
- 💬 AI Debug Assistant — describe your issue in plain text, get a root cause + fix suggestion instantly
- 📈 AI Anomaly Alerts — AI monitors your success rate and flags unusual patterns before they become outages
| Layer | Technology |
|---|---|
| Runtime | Node.js 18+ |
| Framework | Express.js |
| Database | PostgreSQL |
| Queue | BullMQ |
| Cache / Queue Broker | Redis |
| Auth | JWT (access + refresh tokens) |
| HTTP Client | Axios |
| Password Hashing | bcrypt |
| Signature | HMAC-SHA256 (crypto module) |
| AI Layer | Claude API (Anthropic) |
hookrelay/
├── src/
│ ├── config/
│ │ ├── db.js # PostgreSQL connection pool
│ │ └── redis.js # Redis client setup
│ ├── controllers/
│ │ ├── auth.controller.js
│ │ ├── webhook.controller.js
│ │ ├── event.controller.js
│ │ └── log.controller.js
│ ├── middleware/
│ │ ├── auth.middleware.js # JWT verification
│ │ └── ratelimit.middleware.js # Redis-based rate limiter
│ ├── queues/
│ │ ├── delivery.queue.js # BullMQ queue definition
│ │ └── delivery.worker.js # Worker that processes jobs
│ ├── routes/
│ │ ├── auth.routes.js
│ │ ├── webhook.routes.js
│ │ ├── event.routes.js
│ │ └── log.routes.js
│ ├── services/
│ │ ├── hmac.service.js # Signature generation & verification
│ │ ├── delivery.service.js # HTTP POST logic with timeout
│ │ └── dashboard.service.js # Stats aggregation
│ ├── utils/
│ │ └── apiResponse.js # Standardized response format
│ └── app.js # Express app setup
├── worker.js # Worker process entry point
├── server.js # API server entry point
├── .env.example
├── package.json
└── README.md
-- Users table
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
api_key VARCHAR(64) UNIQUE NOT NULL,
secret_key VARCHAR(64) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Webhooks table
CREATE TABLE webhooks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
target_url TEXT NOT NULL,
event_types TEXT[] NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT NOW()
);
-- Events table
CREATE TABLE events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
event_type VARCHAR(100) NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Delivery logs table
CREATE TABLE delivery_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
webhook_id UUID REFERENCES webhooks(id),
event_id UUID REFERENCES events(id),
status VARCHAR(20) NOT NULL, -- pending | success | failed | dead
attempt_number INT DEFAULT 1,
response_code INT,
error_message TEXT,
delivered_at TIMESTAMP
);| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/register |
Register a new user |
| POST | /api/auth/login |
Login and get JWT |
| GET | /api/auth/me |
Get current user info |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/webhooks |
Register a new webhook |
| GET | /api/webhooks |
List all your webhooks |
| PATCH | /api/webhooks/:id |
Update URL or event types |
| DELETE | /api/webhooks/:id |
Delete a webhook |
| PATCH | /api/webhooks/:id/toggle |
Enable / disable |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/events/trigger |
Trigger an event (uses x-api-key header) |
| GET | /api/events |
List past triggered events |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/logs |
All delivery logs for your account |
| GET | /api/logs/:webhook_id |
Logs for a specific webhook |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/ai/analyse-failure/:webhook_id |
AI diagnoses why a webhook keeps failing |
| POST | /api/ai/validate-payload |
AI validates your event payload before sending |
| POST | /api/ai/debug |
Ask AI anything about your webhook system |
| GET | /api/ai/alerts |
Get AI-generated anomaly alerts |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/dashboard/stats |
Summary stats for frontend |
Create a .env file from .env.example:
PORT=3000
DATABASE_URL=postgresql://user:password@localhost:5432/hookrelay
REDIS_URL=redis://localhost:6379
JWT_SECRET=your_super_secret_jwt_key
JWT_REFRESH_SECRET=your_refresh_secret_key- Node.js 18+
- PostgreSQL 15+
- Redis 7+
# Clone the repo
git clone https://github.com/asmit990/hookrelay.git
cd hookrelay
# Install dependencies
npm install
# Setup environment variables
cp .env.example .env
# Edit .env with your DB and Redis credentials
# Run database migrations
npm run migrate
# Start the API server
npm run dev
# In a separate terminal, start the worker
npm run workerHookRelay uses exponential backoff for failed deliveries:
Attempt 1 → immediate
Attempt 2 → wait 3s
Attempt 3 → wait 9s
Attempt 4 → wait 27s
Attempt 5 → wait 81s
─────────────────────────
After 5 failures → status: DEAD → moved to Dead Letter Queue
A delivery is considered failed if:
- The target URL returns a non-2xx response
- The request times out (> 5 seconds)
- The URL is unreachable
Every outgoing webhook request includes a signature header:
X-HookRelay-Signature: sha256=<hmac_hex>
How it's generated:
const signature = crypto
.createHmac('sha256', user.secret_key)
.update(JSON.stringify(payload))
.digest('hex');How to verify on your end (receiver side):
const expectedSig = crypto
.createHmac('sha256', YOUR_SECRET_KEY)
.update(JSON.stringify(req.body))
.digest('hex');
const isValid = `sha256=${expectedSig}` === req.headers['x-hookrelay-signature'];{
"total_webhooks": 12,
"active_webhooks": 10,
"events_today": 143,
"success_rate": "94.2%",
"failed_today": 8,
"dead_webhooks": 2,
"recent_logs": [...]
}{
"dependencies": {
"express": "^4.18.0",
"pg": "^8.11.0",
"redis": "^4.6.0",
"bullmq": "^4.0.0",
"bcrypt": "^5.1.0",
"jsonwebtoken": "^9.0.0",
"axios": "^1.4.0",
"dotenv": "^16.0.0",
"cors": "^2.8.5",
"uuid": "^9.0.0",
"express-rate-limit": "^6.7.0"
}
}Use webhook.site or requestbin.com to get a free test URL that logs all incoming requests — perfect for testing during development.
# Trigger a test event
curl -X POST http://localhost:3000/api/events/trigger \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"event_type": "payment.success", "payload": {"amount": 500, "currency": "INR"}}'This is where HookRelay goes beyond a standard webhook system. Four AI features powered by the Claude API are baked into the backend.
When a webhook hits the Dead Letter Queue, instead of staring at raw error logs, you call one endpoint and get a human-readable explanation of what went wrong and how to fix it.
Endpoint:
POST /api/ai/analyse-failure/:webhook_id
What it does: Sends your last 10 delivery log attempts to Claude AI and asks it to diagnose the issue.
Example response:
{
"analysis": "Your webhook is consistently timing out after 5 seconds. The target URL (api.yoursite.com/hook) appears to be doing synchronous database writes before returning a response. Recommendation: Make your receiver return 200 immediately and process the payload asynchronously using a queue on your side.",
"severity": "high",
"suggested_fix": "Add immediate ACK response pattern on receiver"
}Before you trigger a real event, run your payload through the AI validator to catch structural issues, missing fields, or bad formatting.
Endpoint:
POST /api/ai/validate-payload
Request:
{
"event_type": "payment.success",
"payload": { "amt": 500 }
}AI Response:
{
"valid": false,
"issues": [
"Field 'amt' looks like a typo — did you mean 'amount'?",
"Missing 'currency' field — required for financial events",
"Missing 'transaction_id' — makes debugging impossible without this"
],
"suggested_payload": {
"amount": 500,
"currency": "INR",
"transaction_id": "txn_xxxxxxxx"
}
}A natural language interface to your webhook system. Describe your problem in plain English and get a technical answer back.
Endpoint:
POST /api/ai/debug
Request:
{
"question": "My webhook worked fine yesterday but now it keeps failing with a 401. I didn't change anything."
}AI Response:
{
"diagnosis": "A 401 Unauthorized on the receiver side usually means the HMAC signature verification is failing. This happens when: (1) your secret key was rotated, (2) the payload is being modified before verification (e.g. by a reverse proxy re-encoding JSON), or (3) the receiver is using a different string encoding. Check if your secret_key in .env matches what the receiver is using.",
"steps": [
"Compare secret key on both sides",
"Log raw request body before verification",
"Ensure no middleware is transforming the body"
]
}A cron job runs every hour, feeds your success rate + delivery stats to Claude, and flags any unusual patterns before they turn into real problems.
How it works:
Every 1 hour → cron collects last hour's stats → sends to Claude AI →
if anomaly detected → creates an alert in DB → exposed via /api/ai/alerts
Example alert:
{
"alert": "Your success rate dropped from 98% to 71% in the last hour.
23 of 30 failures are from the same webhook ID (wh_abc123)
targeting payments.yoursite.com. This is likely a deployment
issue on the receiver side, not a HookRelay problem.",
"affected_webhook": "wh_abc123",
"triggered_at": "2025-03-01T14:00:00Z"
}Add these to your .env:
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxx
AI_MODEL=claude-3-haiku-20240307Uses Claude Haiku for speed and cost efficiency — perfect for real-time API responses.
Recommended: Railway (free tier supports Node.js + PostgreSQL + Redis)
# Deploy API server
railway up
# Set environment variables on Railway dashboard
# Add a second service for the worker process (point to worker.js)| Day | Goal |
|---|---|
| 1 | Project setup, DB schema, Express boilerplate |
| 2 | Auth (register, login, JWT, API key generation) |
| 3 | Webhook CRUD endpoints |
| 4 | Redis + BullMQ queue setup |
| 5 | Worker: HTTP delivery + 5s timeout |
| 6 | Retry logic + exponential backoff |
| 7 | HMAC signature generation + delivery logs |
| 8 | Rate limiting + AI features (Failure Analyst, Payload Validator, Debug Assistant) |
| 9 | AI Anomaly Detection cron + vibe code the frontend dashboard |
| 10 | Deploy on Railway + write Postman collection |
Pull requests are welcome. For major changes, open an issue first to discuss what you'd like to change.
Built by asmit · Made with ☕, way too many console.logs, and a little help from AI