Laravel は全 web ルートに CSRF 保護があります。Webhook プロバイダーはセッション cookie も CSRF トークンも送らないため、コントローラ実行前に 419 になります。対策は CSRF 例外と $request->getContent() による raw body 読み取りです。
Webhook ルートを CSRF から除外
Webhook URI を App\Http\Middleware\VerifyCsrfToken の $except に追加(Laravel 11+ は bootstrap/app.php の middleware):
protected $except = [
'webhooks/stripe',
'webhooks/*',
];
server-to-server のパスのみ除外 — アプリ全体の CSRF 無効化は避ける。
署名検証用に raw body を読む
CSRF を回避したら、未変更のバイトを読み取り:
public function handle(Request $request)
{
$payload = $request->getContent(); // raw string
$signature = $request->header('Stripe-Signature', '');
if (!$this->verifySignature($payload, $signature)) {
return response('', 401);
}
$event = json_decode($payload, true, 512, JSON_THROW_ON_ERROR);
$this->processEvent($event);
return response('', 200);
}
getContent() を使い、all() や input() は使わない — Laravel のヘルパーは署名対象の正確なバイトを返しません。
ビジネスロジック前に署名を検証
プロバイダーヘッダーを取得し、raw payload から digest を計算、hash_equals で比較。不一致は 401。署名検証ガイド を参照。
トンネルをセットアップ
- Laravel 起動:
php artisan serve --port=8000。 - 別ターミナル:
npx portpreview 8000。 - トンネル URL + パスをダッシュボードに貼付。
TrustProxiesでトンネルの forwarded ヘッダーを信頼。- テストイベントを送信。
よくある落とし穴
body の二重読み取り
json() 後の getContent() は空。一度読み、検証してからパース。
ミドルウェア順序
コントローラ前のグローバル JSON パーサーはバイトを変える。Webhook ルートを分離。
リトライと冪等性
イベント ID を永続化 — リトライと冪等性のパターン を参照。
さらに学ぶ
基礎はlocalhost トンネリングとローカル webhook デバッグ。署名の仕組みは署名検証ガイド。重複安全なハンドラーはリトライと冪等性のパターン。PortPreview を無料で始める。
