# Stripe webhooks on Laravel

Laravel's CSRF middleware returns 419 for Stripe's POSTs, and reaching for `$request->json()` or `$request->all()` gives parsed data. Exclude the route from CSRF and read `$request->getContent()`.

## The setup

```
// routes/api.php (skips CSRF by default, the simpler home for webhooks)
Route::post('/webhooks/stripe', [StripeWebhookController::class, 'handle']);
```

```
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Stripe\Webhook;
use Stripe\Exception\SignatureVerificationException;

class StripeWebhookController extends Controller
{
    public function handle(Request $request)
    {
        $payload = $request->getContent();
        $sig = $request->header('Stripe-Signature');
        try {
            $event = Webhook::constructEvent(
                $payload, $sig, getenv('STRIPE_WEBHOOK_SIGNING_VALUE')
            );
        } catch (SignatureVerificationException $e) {
            return response('bad signature', 400);
        }
        // switch ($event->type) ...
        return response()->json(['received' => true]);
    }
}
```

If the route must live in `web.php`, add its URI to the `$except` array in the `VerifyCsrfToken` middleware.

## Notes

- `$request->getContent()` returns the raw body string; `$request->all()` is parsed and fails verification.
- `Webhook::constructEvent` is static and needs no API client; verification uses only the signing value.
- Catch the signature exception specifically so a bad signature is a 400, not a 500.