March 13, 2026

Your Laravel Webhooks Are Insecure (How to Fix Them in 10 Minutes)

By Paresh Prajapati • Lead Architect

Your Laravel Webhooks Are Insecure (How to Fix Them in 10 Minutes)

The Open Door Policy

When you integrate a third-party service like Stripe, Razorpay, or GitHub, you rely on webhooks. The external service sends an HTTP POST request to your Laravel API to tell you an event happened (e.g., "Payment Successful").

The easiest way to build this is to create an open POST route, accept the JSON payload, and update your database. This is incredibly dangerous. If your webhook endpoint is public, anyone can send a fake POST request to /api/webhooks/payment with a payload that says {"status": "paid", "user_id": 5}. Without security, you just gave away your product for free.

The Solution: Cryptographic Signatures

You cannot use standard authentication (like Sanctum tokens) for webhooks, because Stripe cannot log in to your app. Instead, secure services use HMAC Signatures.

When Stripe sends a webhook, they sign the payload using a secret key only you and Stripe know. They attach this signature as a custom header (e.g., Stripe-Signature). Your Laravel app must take the incoming payload, encrypt it using your copy of the secret key, and check if your result matches Stripe's header.

Building a Generic Webhook Middleware

Instead of cluttering your controllers with cryptography logic, the architectural best practice is to build a dedicated Laravel Middleware to intercept and verify these requests before they ever reach your application logic.


namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class VerifyWebhookSignature
{
    public function handle(Request $request, Closure $next)
    {
        $signatureHeader = $request->header('X-Provider-Signature');
        $secret = config('services.provider.webhook_secret');

        // Calculate the HMAC hash of the raw request body
        $computedSignature = hash_hmac('sha256', $request->getContent(), $secret);

        // Safely compare the hashes to prevent timing attacks
        if (!hash_equals($computedSignature, $signatureHeader)) {
            abort(401, 'Invalid Webhook Signature');
        }

        return $next($request);
    }
}

Applying the Shield

Once your middleware is registered, securing your routes takes one line of code in your routes/api.php file. You also need to ensure these specific routes are excluded from Laravel's default CSRF protection if you are routing them through the web file.


Route::post('/webhooks/payments', [PaymentController::class, 'handle'])
    ->middleware('verify.webhook');

Conclusion

Security by obscurity (hoping nobody finds your webhook URL) is not a strategy. By implementing cryptographic signature verification via Laravel middleware, you guarantee that your database is only ever updated by the platforms you explicitly trust.

Paresh Prajapati
Lead Architect, Smart Tech Devs