Todos los artículos
Un RestController Spring Boot en Tomcat embebido verifica cabeceras de firma webhook desde payload byte[] crudo recibido por un túnel local.
Spring BootJavawebhook debugginglocal testing

Webhooks en Spring Boot: cuerpo crudo, exención CSRF y cabeceras de firma

Spring Boot facilita endpoints webhook con @RestController, pero Spring Security CSRF y los convertidores JSON pueden bloquear o mutar payloads antes del handler. Las pruebas locales deben demostrar acceso byte[] crudo, exenciones CSRF en rutas webhook y verificación de firma sobre bytes intactos.

Crear un endpoint webhook @RestController

Mantén controladores webhook ligeros. Acepta bytes crudos, verifica firma, luego deserializa:

@RestController
@RequestMapping("/webhooks")
public class StripeWebhookController {

    @PostMapping("/stripe")
    public ResponseEntity handle(
            @RequestBody byte[] payload,
            @RequestHeader("Stripe-Signature") String signature) {

        if (!verifier.isValid(payload, signature)) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        JsonNode event = objectMapper.readTree(payload);
        handler.process(event);
        return ResponseEntity.ok().build();
    }
}

@RequestBody byte[] preserva el payload exacto que Tomcat recibió — ideal cuando el proveedor firma JSON crudo.

Leer cuerpo crudo con HttpServletRequest

Alternativamente, lee directamente de la petición servlet:

byte[] body = request.getInputStream().readAllBytes();
String sig = request.getHeader("Stripe-Signature");

Evita enlazar a un POJO o Map primero — la reserialización Jackson rompe HMAC. Ver guía de verificación de firmas.

Desactivar CSRF solo en rutas webhook

Spring Security habilita CSRF por defecto. Los POST webhook fallan con 403 sin exención server-to-server:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -> csrf.ignoringRequestMatchers("/webhooks/**"));
        return http.build();
    }
}

Limita el ignore a /webhooks/** — no desactives CSRF globalmente.

Verificar cabeceras de firma antes de la lógica de negocio

Lee cabeceras del proveedor, calcula el digest del array de bytes crudo y compara en tiempo constante. 401 si falla, 200 rápido para reducir reintentos.

Flujo de túnel local

  1. Inicia: ./mvnw spring-boot:run en puerto 8080.
  2. Expón Tomcat: npx portpreview 8080.
  3. Pega URL del túnel + ruta en el dashboard.
  4. Envía evento de prueba y revisa logs.
  5. Repite el mismo event ID para idempotencia — ver patrones de reintentos.

Trampas comunes

Orden de filtros y consumo del body

Filtros que leen el stream antes del controlador lo dejan vacío. Usa ContentCachingRequestWrapper si varias capas inspeccionan el body.

Binding global @RequestBody

Un @ControllerAdvice que parsea JSON temprano puede alterar bytes. Aísla endpoints webhook con binding byte[].

Handlers lentos provocan reintentos

Confirma rápido, procesa async, deduplica por event ID.

Para profundizar

Para los fundamentos, lea los fundamentos del tunneling localhost y depuración práctica de webhooks en local. Para la criptografía, consulte la guía de verificación de firmas. Para handlers sin duplicados, lea patrones de reintentos e idempotencia. empiece PortPreview gratis.

Preguntas frecuentes

¿Por qué los webhooks Spring Boot devuelven 403?
CSRF de Spring Security bloquea POST sin token de sesión. Añade csrf.ignoringRequestMatchers para la ruta webhook y verifica la firma.
¿byte[] o POJO para webhooks?
Usa byte[] para verificar firmas. El binding POJO reserializa JSON y rompe HMAC.
¿Cómo probar webhooks Spring Boot en local?
Ejecuta spring-boot:run en 8080, expón con npx portpreview, pega la URL del túnel y dispara eventos de prueba.