すべての記事
VerifyCsrfToken except で CSRF を回避する Laravel webhook POST。getContent のバイトを hash_equals で検証し、ローカルの php artisan serve で処理。
LaravelPHPwebhook debugginglocal testing

Laravel Webhook:CSRF 例外、raw body、署名検証

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。署名検証ガイド を参照。

トンネルをセットアップ

  1. Laravel 起動: php artisan serve --port=8000
  2. 別ターミナル: npx portpreview 8000
  3. トンネル URL + パスをダッシュボードに貼付。
  4. TrustProxies でトンネルの forwarded ヘッダーを信頼。
  5. テストイベントを送信。

よくある落とし穴

body の二重読み取り

json() 後の getContent() は空。一度読み、検証してからパース。

ミドルウェア順序

コントローラ前のグローバル JSON パーサーはバイトを変える。Webhook ルートを分離。

リトライと冪等性

イベント ID を永続化 — リトライと冪等性のパターン を参照。

さらに学ぶ

基礎はlocalhost トンネリングローカル webhook デバッグ。署名の仕組みは署名検証ガイド。重複安全なハンドラーはリトライと冪等性のパターンPortPreview を無料で始める

よくある質問

Laravel が webhook POST で 419 を返すのはなぜ?
VerifyCsrfToken が CSRF トークンなし POST を拒否します。webhook パスを $except に追加し、署名を検証してください。
webhook には getContent() と all() どちら?
署名検証には getContent() を使います。all() と input() はパース済みデータで、署名対象のバイトと一致しません。
Laravel webhook をローカルでテストするには?
php artisan serve を実行し、npx portpreview で公開、トンネル URL をダッシュボードに貼り、テストイベントを送信します。