Spring Boot は @RestController で webhook エンドポイントを簡単に作れますが、Spring Security CSRF と JSON コンバーターが handler 前に payload をブロックまたは変更することがあります。ローカルテストでは byte[] の raw アクセス、webhook パスの CSRF 例外、未変更バイトでの署名検証を確認してください。
@RestController webhook エンドポイントを作成
webhook コントローラーは薄く保ちます。raw バイトを受け取り、署名を検証してからデシリアライズ:
@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[] は Tomcat が受け取った正確な payload を保持 — プロバイダーが raw JSON に署名する場合に最適。
HttpServletRequest で raw body を読む
代替として servlet リクエストから直接読み取り:
byte[] body = request.getInputStream().readAllBytes();
String sig = request.getHeader("Stripe-Signature");
最初に POJO や Map にバインドしない — Jackson の再シリアライズは HMAC を壊します。署名検証ガイド を参照。
webhook パスのみ CSRF を無効化
Spring Security はデフォルトで CSRF を有効化。server-to-server 例外なしでは webhook POST は 403:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.ignoringRequestMatchers("/webhooks/**"));
return http.build();
}
}
ignore を /webhooks/** に限定 — CSRF をグローバルに無効化しない。
ビジネスロジック前に署名ヘッダーを検証
プロバイダーヘッダーを読み、raw byte 配列から digest を計算し、定数時間で比較。不一致は 401、成功は迅速に 200。
ローカルトンネルワークフロー
- 起動:
./mvnw spring-boot:runポート 8080。 - Tomcat を公開:
npx portpreview 8080。 - トンネル URL + パスをダッシュボードに貼付。
- テストイベントを送信してログを確認。
- 同じ event ID を再生して冪等性を確認 — リトライパターン。
よくある落とし穴
フィルター順序と body 消費
コントローラー前に stream を読むフィルターは空にします。必要なら ContentCachingRequestWrapper を使用。
グローバル @RequestBody バインド
早期 JSON パースの @ControllerAdvice はバイトを変える。webhook は byte[] で分離。
遅い handler はリトライを誘発
迅速に ACK、async 処理、event ID で重複排除。
さらに学ぶ
基礎はlocalhost トンネリングとローカル webhook デバッグ。署名の仕組みは署名検証ガイド。重複安全なハンドラーはリトライと冪等性のパターン。PortPreview を無料で始める。
![embedded Tomcat 上の Spring Boot RestController がローカルトンネル経由の raw byte[] payload から webhook 署名ヘッダーを検証。](/_next/image?url=%2Fimages%2Farticles%2Fspring-boot-webhook-local-testing%2Fcover.png&w=3840&q=75)