Spring Boot упрощает webhook-эндпоинты через @RestController, но Spring Security CSRF и JSON-конвертеры могут блокировать или менять payload до handler. Локальное тестирование должно подтвердить доступ к byte[], исключение CSRF для webhook-путей и проверку подписи по нетронутым байтам.
Создайте webhook-эндпоинт @RestController
Держите контроллеры тонкими. Принимайте сырые байты, проверяйте подпись, затем десериализуйте:
@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[] сохраняет точный payload от Tomcat — идеально когда провайдер подписывает сырой JSON.
Читать сырой body через HttpServletRequest
Альтернатива — читать напрямую из servlet-запроса:
byte[] body = request.getInputStream().readAllBytes();
String sig = request.getHeader("Stripe-Signature");
Не биндите сначала к POJO или Map — пересериализация Jackson ломает HMAC. См. руководство по проверке подписи.
Отключить CSRF только для webhook-путей
Spring Security включает CSRF по умолчанию. Webhook POST падают с 403 без исключения server-to-server:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.ignoringRequestMatchers("/webhooks/**"));
return http.build();
}
}
Ограничьте ignore паттерном /webhooks/** — не отключайте CSRF глобально.
Проверять заголовки подписи до бизнес-логики
Читайте заголовки провайдера, вычисляйте digest из byte[] и сравнивайте за константное время. 401 при ошибке, быстрый 200 при успехе.
Локальный workflow с туннелем
- Запуск:
./mvnw spring-boot:runна порту 8080. - Откройте Tomcat:
npx portpreview 8080. - Вставьте URL туннеля + путь в dashboard.
- Отправьте тестовое событие и смотрите логи.
- Повторите event ID для идемпотентности — см. паттерны повторов.
Типичные ловушки
Порядок фильтров и потребление body
Фильтры, читающие stream до контроллера, оставляют пустой поток. Используйте ContentCachingRequestWrapper.
Глобальный @RequestBody binding
Глобальный @ControllerAdvice с ранним JSON-парсингом меняет байты. Изолируйте webhook с byte[].
Медленные handler вызывают повторы
Быстро подтверждайте, обрабатывайте async, дедуплицируйте по event ID.
Дальше
Основы — в основах туннелирования localhost и практической локальной отладке вебхуков. Механика подписей — в руководстве по проверке подписи. Безопасные от повторов обработчики — в паттернах повторов и идемпотентности. начните PortPreview бесплатно.
![RestController Spring Boot на embedded Tomcat проверяет заголовки подписи вебхука из сырого byte[] payload через локальный туннель.](/_next/image?url=%2Fimages%2Farticles%2Fspring-boot-webhook-local-testing%2Fcover.png&w=3840&q=75)