Skip to content

Webhooks with Spring Boot — Complete Guide

DodaTech Updated 2026-06-28 6 min read

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

  1. Not configuring Spring Security to permit webhook endpoints without authentication
  2. Using @RequestBody with a POJO class directly, losing access to the raw payload for signature verification
  3. Blocking the webhook delivery thread with synchronous HTTP calls
  4. Not configuring @EnableAsync and @EnableRetry in the Spring application class
  5. Failing to handle WebClient or RestTemplate exceptions properly
  6. Using String concatenation for HMAC comparison instead of MessageDigest.isEqual
  7. Not setting timeouts on RestTemplate/WebClient for outgoing webhook deliveries

Practice Questions

  1. Why should webhook endpoints be excluded from Spring Security filters?
  2. How does @Async improve webhook provider performance?
  3. What is the advantage of using HandlerInterceptor over inline signature verification?
  4. How would you implement a circuit breaker for webhook delivery in Spring?
  5. Challenge: Build a Spring Boot webhook gateway that accepts webhooks, verifies signatures using a configurable secret per source, routes events to different @Service beans 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

Should I use RestTemplate or WebClient for webhook delivery?

WebClient is preferred for new Spring Boot applications. It supports both synchronous and asynchronous operations and is non-blocking by default. RestTemplate is deprecated in favor of WebClient.

How do I handle webhook payload deserialization in Spring?

Receive the payload as @RequestBody String first, verify the signature, then deserialize with Jackson: new ObjectMapper().readValue(payload, Event.class).

Can I use Spring Cloud Stream for webhook delivery?

Yes. Spring Cloud Stream with Kafka or RabbitMQ can decouple webhook ingestion from processing. This adds persistence and replay capabilities.

How do I configure webhook secrets in Spring Boot?

Store secrets in application.yml or environment variables. Use @ConfigurationProperties to bind them to a POJO. Never hardcode secrets in source code.

How do I monitor webhook delivery in Spring Boot?

Use Micrometer metrics with MeterRegistry. Track delivery count, latency, error rate, and retry attempts. Export to Prometheus and visualize in Grafana.

What is the best way to test Spring Boot webhook endpoints?

Use @WebMvcTest for controller tests, MockMvc to simulate requests, and WireMock to mock external HTTP calls for provider delivery tests.

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