Laravel cookbook · Intermediate

Verify incoming webhook signatures

Anyone can POST fake events to the webhook endpoint.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Verify an HMAC signature over the raw body with a constant-time comparison and a timestamp tolerance in a route middleware, exclude the route from CSRF, and process events idempotently by event ID.

Solution

<?php
// app/Http/Middleware/VerifyWebhookSignature.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class VerifyWebhookSignature
{
    public function handle(Request $request, Closure $next): Response
    {
        $timestamp = (int) $request->header('X-Webhook-Timestamp');
        $signature = (string) $request->header('X-Webhook-Signature');
        $expected = hash_hmac('sha256', $timestamp.'.'.$request->getContent(), config('services.payments.webhook_secret'));

        abort_unless(abs(time() - $timestamp) <= 300, 401, 'Stale webhook');
        abort_unless(hash_equals($expected, $signature), 401, 'Invalid signature');

        return $next($request);
    }
}
<?php
// routes/web.php
use App\Http\Controllers\PaymentWebhookController;
use App\Http\Middleware\VerifyWebhookSignature;
use Illuminate\Support\Facades\Route;

Route::post('/webhooks/payments', PaymentWebhookController::class)->middleware(VerifyWebhookSignature::class);

// bootstrap/app.php
// ->withMiddleware(fn ($middleware) => $middleware->validateCsrfTokens(except: ['webhooks/*']))

// In the controller: WebhookEvent::firstOrCreate(['provider_event_id' => $request->input('id')], [...])
// and only process the event when it was newly created.

Pitfalls

  • Signing json_encode($request->all()) instead of the raw body.
  • == comparison instead of hash_equals.
  • Doing slow work in the webhook request; store the event and process it in a queued job.

Variations

  • Use the provider's SDK verification helper when available (for example Stripe's Webhook::constructEvent).
  • spatie/laravel-webhook-client packages verification, storage and queued processing.

Further reading: Middleware

Reference: https://laravel.com/docs/13.x/middleware#defining-middleware

More Laravel recipes

Was this page helpful?