spatie/laravel-webhook-client
Receive and process incoming webhooks in Laravel. Verify signatures, store webhook payloads, and handle them asynchronously via queued jobs. Includes flexible configuration for endpoints and processing logic.
Start by installing the package via Composer and publishing its config and migration:
composer require spatie/laravel-webhook-client
php artisan vendor:publish --provider="Spatie\WebhookClient\WebhookClientServiceProvider" --tag="webhook-client-config"
php artisan vendor:publish --provider="Spatie\WebhookClient\WebhookClientServiceProvider" --tag="webhook-client-migrations"
php artisan migrate
Next, configure your WEBHOOK_CLIENT_SECRET in .env and update config/webhook-client.php. In routes/web.php, register your webhook endpoint using Route::webhooks('/webhook') and exclude it from CSRF validation. Finally, create a job extending ProcessWebhookJob and set it as the process_webhook_job in the config—this is where you’ll handle the actual business logic (e.g., sync user data, update orders).
Use a single default config for simple apps or define multiple named configs (e.g., stripe, github) in config/webhook-client.php to support multiple webhook sources. Implement custom WebhookProfile classes to filter incoming webhooks by event type (e.g., only(ProcessStripePaymentSucceeded::class)). Store required headers (e.g., X-Request-ID) via store_headers for auditability. Leverage queued jobs for non-blocking processing, and consider augmenting WebhookCall with metadata (e.g., processed_at, error_count) via model observers. For complex apps, wrap your handle() logic in DTOs and services for testability.
VerifyCsrfToken::$except.signing_secret in config exactly matches what the sender uses (case-sensitive, no surrounding quotes in .env).delete_after_days and/or implement a custom WebhookProfile to avoid storing low-value events (e.g., ping).ProcessWebhookJob throws an exception, the WebhookCall model records it in exception. Don’t forget to monitor webhook_calls.exception or set up retries/failures.name key: When defining multiple configs, use unique names (e.g., ['name' => 'stripe']) and point routes via Route::webhooks('/stripe-webhook', 'stripe').pest’s withoutMiddleware('VerifyCsrfToken') or make test calls with ->withHeaders(['Signature' => $validSignature])—but remember to generate signatures identically (hash_hmac('sha256', $payload, $secret)).storeHeaders() on WebhookCall or use model events (e.g., created) to trigger downstream processing.How can I help you explore Laravel packages today?