← Course Index | WebhookFlow — Architecture & Roadmap
Phase 1 Complete
Architecture Reference · WebhookFlow Microservice · Phase 1 → Phase 8

WebhookFlow
Architecture & Implementation Roadmap

A precise map of what is already built (Phase 1 — a self-contained webhook microservice with all real class names) and a phase-by-phase plan for growing it into the full distributed platform.

✅ Phase 1 Complete ☕ Spring Boot 4 / Java 25 🗄️ H2 → PostgreSQL ⚙️ @Async Delivery 🔐 HMAC-SHA256 🏗️ 8-Phase Roadmap
Phase 1 — Complete
What Is Built & Running Now
A single Spring Boot app that does everything — receive, validate, store, and deliver
Single deployable unit: One JAR file, one JVM process, port 8080. Everything from HTTP parsing to database writes to async HTTP delivery happens inside this one Spring Boot application.

This microservice handles five concerns inside one deployable unit

JobHow it's doneKey classes
Receive & route HTTP POST lands on /api/events/receive/{source} EventController
Validate HMAC HMAC-SHA256 signature check before any DB write HmacValidationService
Persist events Save raw payload + metadata to H2 (dev) / PostgreSQL (prod) WebhookEventService · WebhookEventRepository
Async delivery Spring @Async thread pool — HTTP POST to each subscriber, exponential backoff retries EventDeliveryService · SubscriptionService
Observe & manage REST endpoints for events, subscriptions, sources, analytics, and health AnalyticsController · AnalyticsService
Key distinction: WebhookFlow is a microservice — it has one responsibility (webhook platform) and other applications call it via HTTP. It is not a monolith. A monolith would be if your entire product (orders, users, payments, notifications, and webhooks) were all in this same codebase. Phase 1 happens to run as a single Spring Boot process — that is fine for a microservice. Phases 3–8 add internal sub-services (receiver, processor, notifier) and infrastructure, but WebhookFlow always stays a single, focused domain service.
🏗️
Architecture
Layer-by-Layer Stack
Every class in the codebase, organized by its architectural role

Spring Boot enforces a layered architecture. Each layer has one responsibility and depends only on the layer below it. HTTP never talks directly to the database.

External

HTTP Clients

Razorpay · GitHub · Stripe · Postman · Your own services — any system that sends an HTTP POST

↓ HTTP request
Controller

REST API layer — parse HTTP, call service, return JSON

Receives HTTP requests. Reads headers, path variables, request body. Calls the service layer. Never contains business logic.

EventController SubscriptionController SourceController AnalyticsController Health
↓ method calls
Service

Business logic layer — where all decisions are made

All rules live here: signature validation, idempotency check, retry logic, delivery matching, analytics aggregation.

WebhookEventService EventDeliveryService SubscriptionService EventSourceService HmacValidationService AnalyticsService
↓ queries & saves
Repository

Data access layer — Spring Data JPA interfaces

Declare method signatures; Spring generates the SQL. No implementation code needed for standard CRUD and filtered queries.

WebhookEventRepository WebhookSubscriptionRepository DeliveryLogRepository EventSourceRepository
↓ SQL / JPA
Model

Domain entities — JPA-mapped database tables

Each class maps to one database table. Fields become columns. Relationships use JPA annotations.

WebhookEvent WebhookSubscription DeliveryLog EventSource EventStatus (enum)
↔ Azure SQL Database
Database

Azure SQL Database (SQL Server engine)

Managed cloud database at webhookflow-server.database.windows.net:1433. JPA creates tables on startup (ddl-auto=update). Connect from Windows via Azure Data Studio. Credentials in environment variables.

Cross-cutting layers (used by all layers)

DTO

Request / Response objects

Decouple what goes over HTTP from the internal entity model.

ReceiveEventResponse EventResponse DeliveryLogResponse CreateSubscriptionRequest SubscriptionResponse CreateSourceRequest SourceResponse AnalyticsSummaryResponse
Exception

Error handling

Typed exceptions bubble up; one global handler maps them to the right HTTP status codes.

GlobalExceptionHandler ResourceNotFoundException InvalidSignatureException DuplicateEventException
🔄
Code Path
Request Flow — POST /api/events/receive/{source}
Every method call from the moment a webhook arrives to the 200 OK response
Scenario: Razorpay sends a payment.captured webhook to POST /api/events/receive/razorpay. Here is the exact method chain through the codebase.

① Razorpay HTTP POST

Headers: X-Signature, X-Event-Type: payment.captured, X-External-Id: pay_abc123
Body: raw JSON payload

② EventController.receive()

Extracts source = "razorpay" from path, reads raw body as String (preserves bytes for HMAC), reads headers

EventController
↓ calls

③ WebhookEventService.receiveEvent()

Orchestrates the full receive pipeline — source lookup → HMAC → idempotency → persist → trigger delivery

WebhookEventService
↓ calls

④a HmacValidationService.validate()

Computes HMAC-SHA256 of raw body using the source's secret key. Throws InvalidSignatureException if mismatch → 401

HmacValidationService
·

④b Idempotency check

If X-External-Id already in DB, throws DuplicateEventException → 409. Prevents double-processing of retried webhooks.

WebhookEventRepository
↓ both pass

⑤ Persist WebhookEvent

Saves entity with status RECEIVED, raw payload, source name, event type, timestamp. JPA auto-generates id.

WebhookEvent · WebhookEventRepository
↓ saved

⑥ Return 200 OK immediately

Controller returns ReceiveEventResponse with the new event ID. External system gets its response fast — under 50ms typically.

200 OK · { "eventId": "..." }

⑦ @Async delivery fires

Spring's thread pool picks up EventDeliveryService.deliverEvent() in a background thread. The HTTP response is already sent.

EventDeliveryService
⚙️
Background Engine
Async Delivery Engine
What EventDeliveryService does after the 200 OK is already sent

① Match subscriptions

SubscriptionService.findActive(source, eventType) — finds all active WebhookSubscription rows that match this source and event type.

↓ for each subscription

② HTTP POST to targetUrl

Java's HttpClient sends the original payload to the subscriber's registered URL. Timeout: 10 seconds.

↓ always

③ Write DeliveryLog

Records: attempt number, HTTP status code, response body, latency, timestamp. Every attempt — success and failure — is logged.

DeliveryLog · DeliveryLogRepository
↓ if failed

④ Exponential backoff retry

Attempt 1 → wait 1s → Attempt 2 → wait 5s → Attempt 3 → wait 30s → mark FAILED. Total: 3 retries max.

or

④ Update event status

After all subscribers: DELIVERED (all succeeded), PARTIAL (some failed), or FAILED (all failed).

EventStatus enum
Phase 1 limitation: @Async uses an in-process thread pool. If the JVM crashes mid-delivery, in-flight events are lost. Phase 3 replaces this with Azure Service Bus — a durable message queue that survives restarts.
🗄️
Persistence
Data Model — 4 Tables
How the entities relate and what each column does
Table / EntityKey FieldsPurpose
webhook_events
WebhookEvent
id, source, eventType, rawPayload, externalId, status, receivedAt The central log of every event received. externalId is the idempotency key. status tracks delivery lifecycle.
webhook_subscriptions
WebhookSubscription
id, targetUrl, source, eventType, secretKey, active Who wants to receive which events. active flag allows pause without deletion. secretKey used to sign outbound deliveries.
delivery_logs
DeliveryLog
id, eventId (FK), subscriptionId (FK), attemptNumber, statusCode, responseBody, success, attemptedAt Audit trail of every delivery attempt. One row per attempt per subscription. Join to WebhookEvent and WebhookSubscription.
event_sources
EventSource
id, name, secretKey, description, active Registered sources (Razorpay, GitHub, etc.). The secretKey is the HMAC validation key. Must be registered before events can be received.

Entity relationships

EventSource ──has many──> WebhookEvent (one source can send many events) WebhookEvent ──has many──> DeliveryLog (one event → many delivery attempts) WebhookSubscription ──has many──> DeliveryLog (one subscription → many delivery attempts) EventStatus enum values: RECEIVED → event stored, delivery not yet attempted DELIVERED → all subscriptions delivered successfully PARTIAL → some subscriptions delivered, some failed FAILED → all delivery attempts exhausted and failed
🔌
REST API
Full API Surface — 15 Endpoints
All endpoints implemented in Phase 1 — WebhookFlow microservice on port 8080

Event endpoints — EventController

MethodPathWhat it does
POST/api/events/receive/{source}Receive webhook from external system — the main entry point
GET/api/eventsList events with optional filters: ?source=&status=&from=
GET/api/events/{id}Get single event with all its delivery logs
GET/api/events/{id}/deliveriesAll delivery attempts for one event
POST/api/events/{id}/retryRe-trigger delivery for FAILED or PARTIAL events

Subscription endpoints — SubscriptionController

MethodPathWhat it does
POST/api/subscriptionsRegister a new subscriber (targetUrl + source + eventType)
GET/api/subscriptionsList all subscriptions
PUT/api/subscriptions/{id}Update subscription config (URL, secret, filters)
PATCH/api/subscriptions/{id}/toggleEnable or disable a subscription without deleting it
DELETE/api/subscriptions/{id}Remove subscription permanently

Source, Analytics & Health

MethodPathController / What it does
POST/api/sourcesSourceController — register a new event source with HMAC secret
GET/api/sourcesSourceController — list all registered sources
GET/api/analytics/summaryAnalyticsController — event counts by status and source
GET/api/analytics/delivery-rateAnalyticsController — success % per subscription
GET/healthHealth — status + total event count (no Spring Actuator dependency)
🗺️
Implementation Plan
Phase-by-Phase Roadmap
From the current single-process deployment to the full distributed platform — one phase at a time
Phase 1
WebhookFlow Microservice — Core Webhook Platform (single process)
✅ Complete
  • Full layered architecture: Controller → Service → Repository → JPA → H2
  • HMAC-SHA256 signature validation on all incoming events
  • Idempotency via externalId deduplication
  • @Async delivery engine with exponential backoff (1s → 5s → 30s)
  • DeliveryLog audit trail for every attempt
  • Full REST API: 15 endpoints across 4 controllers
  • AnalyticsService: delivery rates, status counts
Spring Boot 4 Java 25 H2 in-memory Spring Data JPA @Async HMAC-SHA256
Phase 2
Production Database + Caching + Observability
Up Next
  • Swap H2 → PostgreSQL (3-line change in application.properties + add driver dependency)
  • Add Redis for idempotency key cache (sub-millisecond duplicate check vs. DB query)
  • Add Spring Actuator: /actuator/health, /actuator/metrics, /actuator/info
  • Structured JSON logging with correlation IDs (trace a single event across all log lines)
  • Add Flyway or Liquibase for database migrations
PostgreSQL Redis Spring Actuator Flyway Structured Logging
Phase 3
Message Queue — Extract Event Processor Service
Planned
  • Replace @Async in-process delivery with Azure Service Bus queue
  • Receiver publishes event message to queue → returns 200 OK immediately
  • New event-processor service subscribes to queue and handles delivery independently
  • Events are durable: if processor crashes, messages wait in queue and replay on restart
  • Scale receiver and processor independently (receive spikes ≠ processing spikes)
Azure Service Bus event-processor (new service) Message-driven architecture Durable delivery
Phase 4
Notification Service — Email, SMS & Slack Alerts
Planned
  • New notification-service (port 8082) — email via Amazon SES, SMS via Twilio, Slack via webhooks
  • Event processor publishes a notification event after FAILED delivery
  • Operators get alerted when a subscriber's delivery keeps failing
  • Subscribers can self-configure notification preferences via the API
notification-service Amazon SES Twilio SMS Slack API
Phase 5
Docker — Containerize Every Service
Planned
  • Write a Dockerfile for each service (multi-stage build: compile → minimal JRE image)
  • docker-compose.yml spins up all services + PostgreSQL + Redis + Service Bus emulator locally
  • No more "it works on my machine" — same container image runs locally and in Azure
  • Add GitHub Actions CI/CD: build → test → push image to Azure Container Registry on merge to main
Docker docker-compose GitHub Actions Azure Container Registry
Phase 6
Kubernetes — Deploy to Azure AKS
Planned
  • Create AKS cluster on Azure; write Kubernetes YAML manifests (Deployment, Service, Ingress, ConfigMap, Secret)
  • API Gateway (NGINX Ingress or Azure API Management) routes external traffic to services
  • Horizontal Pod Autoscaler: scale receiver pods up during traffic spikes, down during quiet periods
  • Rolling deployments — zero downtime upgrades; rollback with one kubectl command
  • Azure Monitor + Application Insights for dashboards and alerting
AKS Kubernetes manifests NGINX Ingress HPA Azure Monitor
Phase 7
React Dashboard — Live Event Feed & Analytics UI
Planned
  • React + TypeScript SPA (port 3000) — consumes the existing REST API
  • Live event feed using Server-Sent Events (SSE) or polling every 3 seconds
  • Subscription manager: create, toggle, delete subscriptions without Postman
  • Delivery status timeline: see every retry attempt for any event
  • Analytics charts: delivery rate per subscription, events per source over time
React + TypeScript Live event feed Recharts SSE
Phase 8
AI / LLM — Intelligent Event Classification & Query
Planned
  • New ai-service (port 8083): LangChain4j + Azure OpenAI GPT-4o
  • Natural language queries: "Show me all failed payments from Razorpay in the last hour" → translates to API call
  • Automatic event classification: inspect payload → label event type + severity + suggested action
  • Anomaly detection: flag unusual event patterns (spike in failures, unknown source IDs)
  • LLM-generated delivery failure summaries: "5 of 8 subscribers failed due to connection timeout on targetUrl X"
LangChain4j Azure OpenAI GPT-4o ai-service NL queries Anomaly detection
The teaching insight: Every phase adds a new concept without throwing away what students already understand. Phase 8 students still recognize the same EventController they wrote in Phase 1 — they have just surrounded it with a production-grade system that handles real-world scale.