Todos os artigos
Um RestController Spring Boot no Tomcat embebido verifica cabeçalhos de assinatura webhook de payload byte[] bruto recebido via túnel local.
Spring BootJavawebhook debugginglocal testing

Webhooks no Spring Boot: corpo bruto, isenção CSRF e cabeçalhos de assinatura

O Spring Boot facilita endpoints webhook com @RestController, mas Spring Security CSRF e conversores JSON podem bloquear ou alterar payloads antes do handler. Testes locais devem provar acesso byte[] bruto, isenções CSRF em paths webhook e verificação de assinatura em bytes intactos.

Criar endpoint webhook @RestController

Mantenha controladores webhook leves. Aceite bytes brutos, verifique assinatura, depois deserialize:

@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 o payload exato que o Tomcat recebeu — ideal quando o provedor assina JSON bruto.

Ler corpo bruto com HttpServletRequest

Alternativamente, leia diretamente do pedido servlet:

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

Evite bind a POJO ou Map primeiro — reserialização Jackson quebra HMAC. Veja guia de verificação de assinatura.

Desativar CSRF apenas em paths webhook

Spring Security ativa CSRF por defeito. POSTs webhook falham com 403 sem isenção server-to-server:

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

Limite ignore a /webhooks/** — não desative CSRF globalmente.

Verificar cabeçalhos de assinatura antes da lógica de negócio

Leia cabeçalhos do provedor, calcule digest do array de bytes bruto e compare em tempo constante. 401 se falhar, 200 rápido no sucesso.

Fluxo de túnel local

  1. Inicie: ./mvnw spring-boot:run na porta 8080.
  2. Exponha Tomcat: npx portpreview 8080.
  3. Cole URL do túnel + path no dashboard.
  4. Envie evento de teste e veja logs.
  5. Repita o mesmo event ID para idempotência — veja padrões de retry.

Armadilhas comuns

Ordem de filtros e consumo do corpo

Filtros que leem stream antes do controller deixam stream vazio. Use ContentCachingRequestWrapper se necessário.

Binding global @RequestBody

@ControllerAdvice que faz parse JSON cedo pode alterar bytes. Isole webhooks com byte[].

Handlers lentos provocam retries

Confirme rápido, processe async, deduplique por event ID.

Para aprofundar

Para fundamentos, leia noções de tunneling localhost e depuração local de webhooks. Para mecânica de assinatura, veja o guia de verificação de assinatura. Para handlers sem duplicatas, leia padrões de retry e idempotência. comece PortPreview grátis.

Perguntas frequentes

Por que webhooks Spring Boot retornam 403?
CSRF do Spring Security bloqueia POST sem token de sessão. Adicione csrf.ignoringRequestMatchers para o path webhook e verifique a assinatura.
byte[] ou POJO para webhooks?
Use byte[] para verificação de assinatura. Binding POJO reserializa JSON e quebra HMAC.
Como testar webhooks Spring Boot localmente?
Execute spring-boot:run na porta 8080, exponha com npx portpreview, cole a URL do túnel e dispare eventos de teste.