Build WebhookFlow —
A Real-World Webhook Platform
One project, all course concepts. We start with plain Java classes and grow this into a production-grade microservices platform on Azure Kubernetes — step by step, week by week.
A webhook is an HTTP POST request that one system sends to another when something happens. Razorpay sends a webhook when a payment succeeds. GitHub sends one when code is pushed. Zomato sends one when an order status changes.
WebhookFlow is the platform that sits in the middle — it receives these events, validates them, stores them, and forwards them to whichever internal services need to act on them.
The problem it solves
| Problem | How WebhookFlow solves it |
|---|---|
| Multiple services receive the same event | One receiver → fan out to all subscribers via queue |
| External system sends event but our service is down | Event stored in DB; retry with exponential backoff |
| Fake / tampered events from malicious sources | HMAC-SHA256 signature validation on every request |
| No visibility into what events arrived and when | Full event log, delivery status, and analytics dashboard |
| Single webhook handler becomes a bottleneck under load | Horizontally scale receiver pods on Kubernetes |
| Company / API | Event | Webhook Payload Example | Action Triggered |
|---|---|---|---|
| Razorpay | payment.captured | orderId, amount, method, userId | Mark order as paid, send invoice email |
| Razorpay | payment.failed | orderId, errorCode, reason | Notify customer, unlock cart, alert team |
| GitHub | push | repo, branch, commits, author | Trigger CI/CD pipeline (GitHub Actions) |
| GitHub | pull_request.opened | title, author, base branch | Run code review bot, assign reviewers |
| Stripe | invoice.payment_failed | customerId, amount, retryCount | Suspend subscription, send dunning email |
| Zomato / Swiggy | order.status_changed | orderId, status, driverId, eta | Push notification to customer app |
| Twilio | message.received | from, body, mediaUrl | Process incoming SMS, run chatbot |
| Your company | user.registered | userId, email, plan | Send welcome email, setup trial, log analytics |
- Accept POST /webhook/receive/{source}
- HMAC-SHA256 signature validation
- Parse JSON payload with Jackson
- Return 200 OK immediately (async)
- Idempotency — ignore duplicate events
- Save every event to PostgreSQL
- Track status: RECEIVED → PROCESSING → DELIVERED / FAILED
- Full delivery log per attempt
- Indexed queries by event type, status, date
- Retention policy (auto-delete after 90 days)
- Match event type to registered subscriptions
- Forward event via HTTP POST to subscriber URL
- Retry failed deliveries (3 attempts)
- Exponential backoff: 1s → 5s → 30s
- Dead letter queue for permanently failed events
- React dashboard with live event feed
- Delivery success rate per subscription
- Event volume charts (hourly/daily)
- Failed event list with retry button
- AI: natural language event queries
- HMAC-SHA256 signature verification per source
- Secrets stored in Azure Key Vault
- Rate limiting on receiver endpoint
- IP allowlist per source (optional)
- Stateless receiver — scale horizontally
- Azure Service Bus decouples receive from process
- Kubernetes HPA scales receiver pods with load
- Redis cache for subscription lookup (fast)
Any HTTP client
API Gateway
Route · Auth · Rate limit
Port 8000 (AKS)
webhook-receiver
Validate signature
Parse + save event
Publish to queue
Port 8080
event-processor
Read from queue
Match subscriptions
HTTP deliver to targets
Retry on failure · Port 8081
notification-service
Email alerts (SES)
SMS via Twilio
Slack notifications
Port 8082
Azure Service Bus
Message queue between receiver and processor
Guarantees delivery
PostgreSQL / Azure SQL
webhook_events
subscriptions
delivery_logs
Redis Cache
Subscription lookup cache
Idempotency keys
Rate limit counters
webhook-dashboard
React + TypeScript
Live event feed · Subscription manager
Analytics charts · Port 3000
AI Service
LangChain4j + Azure OpenAI
Natural language queries
Event classification · Port 8083
payment.captured webhook to your server. Here is every step that follows.Razorpay sends HTTP POST
Razorpay makes a POST request to https://webhookflow.yourdomain.com/api/events/receive/razorpay with a JSON body and an X-Razorpay-Signature header.
Signature Validation
The receiver computes HMAC-SHA256 of the raw request body using the Razorpay secret key (fetched from Azure Key Vault) and compares it to the header value. If they don't match → reject immediately with 401. This prevents fake events from malicious actors.
If the eventId was already processed → return 200 OK immediately (idempotent) — no duplicate processing.
webhook-receiver · SignatureValidator.javaParse & Map to Domain Object
Jackson deserializes the JSON body. The receiver maps it to a WebhookEvent Java object with status = RECEIVED.
Save to Database
The event is persisted to the webhook_events table in PostgreSQL via Spring Data JPA. This is the safety net — even if everything downstream crashes, the event is safe and can be reprocessed.
Publish to Message Queue
The event ID is published to an Azure Service Bus queue. This decouples receiving from processing — the receiver's job is done and it returns immediately.
Azure Service Bus · EventPublisher.javaReturn 200 OK to Razorpay — Immediately
Razorpay (and all webhook senders) expect a fast 200 OK response. If your server takes >5 seconds, they consider it failed and retry. By returning 200 right after saving to DB + queue, we guarantee fast response regardless of how long downstream processing takes.
Best Practice: Accept Fast, Process Asyncevent-processor Picks Up from Queue
The event-processor service is listening to the Service Bus queue. It picks up the event ID, fetches the full event from DB, and updates status to PROCESSING.
Match Subscriptions
The processor queries the subscriptions table for all active subscriptions that listen to payment.captured events. Typically: Order Service, Email Service, Analytics Service.
Deliver to Each Subscriber
For each matching subscription, the processor makes an HTTP POST to the subscriber's targetUrl with the event payload. A DeliveryLog entry is saved for each attempt — recording the response status, body, and timestamp.
If the subscriber returns 2xx → success. Anything else → retry.
event-processor · DeliveryService.javaRetry on Failure (Exponential Backoff)
If delivery fails, the processor waits and retries:
- Attempt 1 — immediate
- Attempt 2 — wait 1 second
- Attempt 3 — wait 5 seconds
- Attempt 4 — wait 30 seconds
- After 4 failures — status = FAILED, event goes to dead-letter queue, team alerted
Notification Service Triggered
For a payment.captured event: send a confirmation email to Rahul ("Your payment of ₹2,499 was successful") and trigger a Slack alert to the operations team. The notification-service listens on a separate Service Bus topic.
Dashboard Updates
The React dashboard polls GET /api/events?limit=20 every 5 seconds. The new event appears in the live feed with status DELIVERED and green badge. Rahul's payment is visible in the events log.
EventStatus Enum
Event Ingestion
Event Management
Subscription Management
Event Sources
Analytics
AI Queries
- Install VS Code, JDK 21, Maven
- VS Code Spring Boot extensions
- Git init, first commit, GitHub repo
- Create GitHub repo: webhookflow
- Generate Spring Boot project (start.spring.io)
- First
git pushof the skeleton project - H2 in-memory DB connected and running
- Variables, conditions, loops, methods
- Classes, constructors, this keyword
- Inheritance, polymorphism, interfaces
- ArrayList, HashMap, for-each
- Lambda, Stream API, Optional
- Exception handling, custom exceptions
- WebhookEvent.java — class, fields, constructor
- EventStatus.java — enum (RECEIVED, DELIVERED, FAILED)
- WebhookSubscription.java — domain model
- DeliveryLog.java — nested object
- InMemoryEventStore.java — ArrayList store, search with Streams
- SignatureValidator.java — utility with static methods
- EventNotFoundException.java — custom exception
- @SpringBootApplication, @RestController
- @Service, @Component, constructor DI
- @PostMapping, @GetMapping, @RequestBody
- application.properties configuration
- Spring Boot DevTools auto-restart
- WebhookController.java — first POST endpoint
- EventService.java — business logic layer with @Service
- SignatureService.java — HMAC validation, injected into controller
- Configure server.port, application name in properties
- Test POST /api/events/receive/razorpay in Postman
- See "Event received!" response — milestone ✅
- @Entity, @Id, @Column, @Enumerated
- JpaRepository<T, ID>
- findByX(), @Query with JPQL
- @OneToMany, @ManyToOne relationships
- Lazy vs Eager loading
- Add @Entity to WebhookEvent, WebhookSubscription, DeliveryLog
- WebhookEventRepository.java — extends JpaRepository
- findByStatus(), findBySourceAndEventType(), findByReceivedAtAfter()
- DeliveryLog @ManyToOne → WebhookEvent relationship
- Events now persist to H2 → switch to PostgreSQL
- HTTP methods, status codes, URL design
- @RestControllerAdvice, @ExceptionHandler
- @Valid, @NotNull, @Size validation
- DB schema design, indexes, transactions
- Postman collections, automated tests
- Full CRUD REST API for Subscriptions
- GlobalExceptionHandler.java — returns clean JSON errors
- SubscriptionRequest.java — DTO with @Valid annotations
- DB indexes on event_type, status, received_at (query speed)
- Postman collection with 12 requests + tests
- Single-process service vs distributed platform tradeoffs
- Service boundaries — what to split
- Synchronous (REST) vs async (queue)
- Spring RestTemplate / WebClient
- Azure Service Bus integration
- Extract event-processor-service as a new Spring Boot project
- EventPublisher.java — publish to Azure Service Bus on receive
- EventConsumer.java — @ServiceBusListener in processor service
- Receiver returns 200 OK instantly; processor runs async
- Both services share the same PostgreSQL database
- Azure Portal: Resource Groups, App Service
- Azure SQL Database (managed PostgreSQL)
- Azure Key Vault — secret management
- Azure Service Bus queues and topics
- Azure CLI deployment commands
- Deploy webhook-receiver to Azure App Service
- Move DB from local PostgreSQL → Azure SQL
- Store HMAC secret keys in Azure Key Vault
- Service Bus queue created in Azure portal
- First live webhook test: Razorpay → Azure → DB ✅
- Dockerfile for Spring Boot (multi-stage build)
- docker-compose for local dev environment
- Environment variables in containers
- Push image to Azure Container Registry
- Dockerfile for webhook-receiver and event-processor
- docker-compose.yml — receiver + processor + PostgreSQL + Redis
- One command:
docker compose up→ full stack running locally - Push images to Azure Container Registry (ACR)
- Kubernetes Deployments, Services, ConfigMaps, Secrets
- HorizontalPodAutoscaler (HPA)
- Rolling update deployments
- GitHub Actions CI/CD pipeline
- kubectl commands for operations
- k8s/receiver-deployment.yaml — 2 replicas, readiness probe
- k8s/processor-deployment.yaml
- HPA for receiver: scale 2→10 pods when CPU > 70%
- GitHub Actions pipeline: push code → run tests → build Docker → push ACR → deploy to AKS
- Full production deployment on AKS ✅
- React components, props, state
- useState, useEffect hooks
- TypeScript interfaces for API responses
- Calling REST APIs with fetch / axios
- React Query for data fetching + caching
- EventFeed.tsx — live list, auto-refreshes every 5s
- EventDetail.tsx — drawer with raw JSON, delivery logs, retry button
- SubscriptionManager.tsx — CRUD UI for subscriptions
- AnalyticsDashboard.tsx — event volume chart, delivery rate cards
- Status badges: 🟢 DELIVERED · 🔴 FAILED · 🟡 PROCESSING
- What is an LLM / embedding / RAG
- LangChain4j in Spring Boot
- Prompt engineering for structured output
- Function calling / tool use
- Azure OpenAI vs OpenAI API
- EventClassifier.java — AI classifies payload: PAYMENT / AUTH / NOTIFICATION
- AiQueryController.java — POST /api/ai/query: "show failed payments last hour" → real DB results
- AI generates human-readable event summary: "₹2,499 UPI payment by Rahul captured"
- Anomaly detection: alert if failure rate > 20% in 10 min window
- AI Q&A on dashboard: type a question, get an answer
Phase 1 — Weeks 1–3: WebhookFlow Microservice (single process)
Phase 2 — Weeks 4–6: Split into Services
| Service | Responsibilities | Triggers | Port |
|---|---|---|---|
| webhook-receiver | Accept POST, validate signature, save event, publish to queue, return 200 | External HTTP | 8080 |
| event-processor | Read from queue, match subscriptions, deliver, save delivery log, retry | Service Bus queue | 8081 |
| notification-service | Send email/SMS for critical events (payment failures, alerts) | Service Bus topic | 8082 |
Phase 3 — Weeks 7–8: Full Production System
| Service | Language/Tech | Deployment | Scales |
|---|---|---|---|
| webhook-receiver | Spring Boot 3 + Java 21 | AKS — 2 pods | Yes — HPA 2→10 |
| event-processor | Spring Boot 3 + Java 21 | AKS — 1 pod | Manual |
| notification-service | Spring Boot 3 + Java 21 | AKS — 1 pod | No |
| ai-service | Spring Boot + LangChain4j | AKS — 1 pod | No |
| webhook-dashboard | React 18 + TypeScript | Azure Static Web Apps | CDN |
| PostgreSQL | Azure SQL (managed) | Azure — PaaS | Azure managed |
| Redis | Azure Cache for Redis | Azure — PaaS | Azure managed |
| Category | Technology | Used for in WebhookFlow | Week |
|---|---|---|---|
| Language | Java 21 (LTS) | All backend services | Week 1 |
| Framework | Spring Boot 3.x | REST API, DI, auto-config | Week 2 |
| ORM | Spring Data JPA + Hibernate | Database access layer | Week 3 |
| Database | PostgreSQL / Azure SQL | Persist all events, subscriptions, logs | Week 3 |
| Cache | Redis (Azure Cache) | Subscription lookup, idempotency keys | Week 4 |
| Messaging | Azure Service Bus | Decouple receiver from processor | Week 4 |
| Secret Mgmt | Azure Key Vault | HMAC secrets, DB passwords | Week 5 |
| Monitoring | Azure Application Insights | Request tracing, error alerts | Week 5 |
| Container | Docker | Package each service as a container | Week 5 |
| Orchestration | Kubernetes / AKS | Deploy and scale all services | Week 6 |
| CI/CD | GitHub Actions | Automated test → build → deploy pipeline | Week 6 |
| Frontend | React 18 + TypeScript | Event dashboard, subscription UI | Week 7 |
| Frontend State | React Query (TanStack) | Data fetching, caching, auto-refresh | Week 7 |
| Styling | Tailwind CSS | Dashboard UI | Week 7 |
| AI/LLM | LangChain4j + Azure OpenAI | Event classification, NL queries | Week 8 |
| Build | Maven | Dependency management, packaging | Pre-Course |
| IDE | VS Code + Extensions | Development environment | Pre-Course |
| API Testing | Postman | Test all REST endpoints | Week 2 |
| Version Control | Git + GitHub | Source code, PRs, history | Pre-Course |