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
- Inicie:
./mvnw spring-boot:runna porta 8080. - Exponha Tomcat:
npx portpreview 8080. - Cole URL do túnel + path no dashboard.
- Envie evento de teste e veja logs.
- 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.
![Um RestController Spring Boot no Tomcat embebido verifica cabeçalhos de assinatura webhook de payload byte[] bruto recebido via túnel local.](/_next/image?url=%2Fimages%2Farticles%2Fspring-boot-webhook-local-testing%2Fcover.png&w=3840&q=75)