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 — reserializacja 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 тунnelю + шлях у 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)