Webhooks with Spring Boot — Complete Guide
In this tutorial, you will learn about Webhooks with Spring Boot. We cover key concepts, practical examples, and best practices to help you master this topic.
Spring Boot provides a mature ecosystem for building webhook systems in Java. With its dependency injection, asynchronous task execution, and robust HTTP clients, Spring Boot is an excellent choice for enterprise webhook infrastructure. This lesson covers provider and consumer patterns specific to the Spring Boot ecosystem.
What You'll Learn
- Create Spring Boot REST endpoints for webhook consumption
- Implement HMAC signature verification with Spring filters
- Use RestTemplate or WebClient for webhook delivery
- Leverage Spring Async for non-blocking webhook dispatch
Why It Matters
Java and Spring Boot dominate enterprise environments where webhook reliability, transactionality, and observability are critical. Spring Boot's comprehensive toolset simplifies the implementation of production-grade webhook systems with proper error handling, monitoring, and security controls.
Real-World Use
- Enterprise payment systems use Spring Boot webhook consumers for transaction reconciliation
- Banking platforms send account notification webhooks via Spring Boot services
- Supply chain systems use Spring Boot webhook providers for inventory event distribution
- Healthcare platforms leverage Spring Boot's transactional guarantees for webhook processing
Mermaid Flow
graph TD
A[Spring Boot App] --> B[Controller Endpoint]
B --> C[Signature Verification Filter]
C --> D[Service Layer]
D --> E{Event Type}
E -->|Payment| F[Payment Handler]
E -->|User| G[User Handler]
E -->|Inventory| H[Inventory Handler]
F --> I[@Async Dispatcher]
I --> J[WebClient POST]
J --> K[Update Delivery Log]
Teacher's Corner
Focus on Spring-specific patterns: @RestController for endpoints, HandlerInterceptor for signature verification, @Async for non-blocking delivery, and WebClient for reactive HTTP calls. Compare Spring's approach to Express.js"Express" >}}.js and Django. Show how Spring's transaction management can help with exactly-once webhook processing.
Code Examples
Example 1: Webhook Consumer Controller
package com.example.webhook;
import org.springframework.web.bind.annotation.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Map;
@RestController
@RequestMapping("/webhook")
public class WebhookController {
private static final String SECRET = "whsec_your_secret";
@PostMapping
public Map<String, Object> receiveWebhook(
@RequestBody String payload,
@RequestHeader("X-Signature-256") String signature) throws Exception {
String expected = computeSignature(payload);
if (!expected.equals(signature.replace("sha256=", ""))) {
return Map.of("error", "invalid signature");
}
System.out.println("Received webhook: " + payload);
return Map.of("status", "received");
}
private String computeSignature(String payload) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(
SECRET.getBytes(), "HmacSHA256");
mac.init(keySpec);
byte[] hash = mac.doFinal(payload.getBytes());
return Base64.getEncoder().encodeToString(hash);
}
}
Expected Output: POST /webhook with valid signature returns {"status": "received"}. Invalid signature returns 401.
Example 2: Webhook Delivery with Async and Retry
package com.example.webhook;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
@Service
public class WebhookDeliveryService {
private final WebClient webClient;
public WebhookDeliveryService() {
this.webClient = WebClient.create();
}
@Async
@Retryable(
value = RuntimeException.class,
maxAttempts = 5,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
public void deliver(String url, String payload, String signature) {
webClient.post()
.uri(url)
.header("Content-Type", "application/json")
.header("X-Signature-256", signature)
.bodyValue(payload)
.retrieve()
.toBodilessEntity()
.subscribe(
response -> System.out.println("Delivered to " + url),
error -> {
System.err.println("Failed: " + error.getMessage());
throw new RuntimeException("Retry");
}
);
}
}
Expected Output: Webhook delivery with retry on failure. Logs success or throws for retry.
Example 3: Signature Verification HandlerInterceptor
package com.example.webhook;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.util.Base64;
import java.util.stream.Collectors;
@Component
public class SignatureInterceptor implements HandlerInterceptor {
private static final String SECRET = "whsec_your_secret";
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (!"POST".equalsIgnoreCase(request.getMethod())) {
return true;
}
String signature = request.getHeader("X-Signature-256");
if (signature == null) {
response.sendError(401, "Missing signature");
return false;
}
String body = new BufferedReader(request.getReader())
.lines().collect(Collectors.joining("\n"));
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(
SECRET.getBytes(), "HmacSHA256");
mac.init(keySpec);
String expected = Base64.getEncoder()
.encodeToString(mac.doFinal(body.getBytes()));
if (!expected.equals(signature.replace("sha256=", ""))) {
response.sendError(401, "Invalid signature");
return false;
}
request.setAttribute("rawBody", body);
return true;
}
}
Expected Output: All incoming POST requests to protected endpoints have their signature verified automatically. Invalid signatures return 401 before reaching the controller.
Common Mistakes
- Not configuring Spring Security to permit webhook endpoints without authentication
- Using
@RequestBodywith a POJO class directly, losing access to the raw payload for signature verification - Blocking the webhook delivery thread with synchronous HTTP calls
- Not configuring
@EnableAsyncand@EnableRetryin the Spring application class - Failing to handle WebClient or RestTemplate exceptions properly
- Using String concatenation for HMAC comparison instead of
MessageDigest.isEqual - Not setting timeouts on RestTemplate/WebClient for outgoing webhook deliveries
Practice Questions
- Why should webhook endpoints be excluded from Spring Security filters?
- How does
@Asyncimprove webhook provider performance? - What is the advantage of using
HandlerInterceptorover inline signature verification? - How would you implement a circuit breaker for webhook delivery in Spring?
- Challenge: Build a Spring Boot webhook gateway that accepts webhooks, verifies signatures using a configurable secret per source, routes events to different
@Servicebeans by event type, delivers webhooks to downstream consumers with retry and circuit breaker, stores delivery logs in JPA, and exposes metrics via Micrometer.
Answer Key
1. Webhook providers do not have application-specific auth tokens. The endpoint must be publicly accessible. Exclude the path in `SecurityConfig` or use the signature header as authentication. 2. @Async moves delivery to a separate thread pool, freeing the HTTP handler thread to respond quickly. This prevents provider timeouts and improves throughput. 3. HandlerInterceptor centralizes verification logic for all webhook endpoints. It runs before the controller, so unverified requests never reach the business logic. 4. Use Spring Cloud Circuit Breaker with Resilience4j. Annotate the delivery method with `@CircuitBreaker`. Configure failure threshold and Sliding Window in application.yml. 5. Create a WebhookSource entity mapped to secrets, a SignatureFilter reading the source header, a RouterService using a Map of event type to handler beans, a WebhookDeliveryService with `@Async`, `@Retryable`, and `@CircuitBreaker`, a DeliveryLog JPA entity, and Micrometer metrics for delivery count, latency, and error rate.FAQ
Mini Project
Build a Spring Boot webhook management service. Create: (1) a WebhookSubscription JPA entity with encrypted secret storage, (2) a REST controller for subscription CRUD, (3) a consumer endpoint with signature verification via HandlerInterceptor, (4) an async delivery service with retry and circuit breaker, (5) a DeliveryLog entity for audit trail, (6) a scheduled job to retry failed deliveries, and (7) Micrometer metrics for delivery monitoring.
What's Next
Now that you understand webhook implementation patterns, learn about storing webhook events in a database for audit trails and replay.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro