Objective: Build a tiny pipeline that accepts third‑party webhooks, queues jobs in Redis, and sends FCM notifications. Includes a minimal Next.js dashboard to inspect and replay events.
Webhook -> API (HMAC, timestamp, idempotency, RL) -> Redis Stream (orders-stream)
-> Worker (Streams CG, enrich user -> device token, FCM w/ backoff) -> sent|DLQ
-> Admin UI (/admin/events) list, search, replay
-
Clone and install
npm i cp .env.example .env # fill .env values
-
Start services
# in one terminal npm run dev # in a second terminal npm run worker
-
(Optional) Start Redis with Docker
docker compose up -d
-
Send a signed webhook
WEBHOOK_SECRET=$(grep WEBHOOK_SECRET .env | cut -d= -f2) BODY='{"event_id":"evt_12345","type":"order.created","data":{"order_id":"ord_987","userId":"user_42","amount":1599}}' SIG=$(printf "%s" "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -hex | sed 's/^.* //') TS=$(date +%s) curl -X POST http://localhost:3000/api/webhook/order.created -H "Content-Type: application/json" -H "X-Signature: $SIG" -H "X-Timestamp: $TS" -d "$BODY"
POST /api/webhook/order.created
— validates HMAC & timestamp, rate‑limits, idempotency (24h), enqueues to Redis Streams, responds ≤300ms.GET /api/health
—{ ok: true, redis: "up" }
if Redis reachable.GET /api/metrics
— counters:received, deduped, sent, failed, dlq
.GET /api/admin/events
— last 20 events (or?event_id=...
to search).POST /api/admin/replay
— requeue a failed event byevent_id
.
- Streams Consumer Group (
orders-group
), reads fromorders-stream
. - Enrich: resolve
fcm:token:<userId>
from Redis to FCM device token. - FCM notification: Title: New Order; Body: Order
<order_id>
placed by<userId>
; target device token or topicorders
when none. - Retries with exponential backoff
1s → 4s → 10s
(3 attempts), then to DLQorders-dlq
.
/admin/events
lists recent events and statusqueued | processing | sent | failed
.- Search by
event_id
. - Replay button requeues a failed event.
- HMAC computed over raw request body (
raw-body
), constant‑time compare. X-Timestamp
must be within 5 minutes.- Idempotency:
SET idempotent:<event_id> EX 86400 NX
. - Rate limit:
10 req / 10s
per IP via Redis counter TTL=10s. FCM_DRY_RUN=true
logs instead of sending real pushes.- No Bull/BullMQ.
npm test
(Jest)- Signature verification (good/bad)
- Timestamp window
- Idempotency (duplicate event_id via NX)
- Retry backoff calculation
Built on 2025-09-09.