Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Webhook Laravel Package

symfony/webhook

Symfony Webhook component for sending and consuming webhooks. Helps build webhook endpoints, verify and parse incoming requests, and dispatch outgoing webhooks with consistent signatures and payload handling across integrations.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup for Laravel

  1. Install the package and Laravel adapter:

    composer require symfony/webhook spatie/laravel-webhook-server
    php artisan vendor:publish --provider="Spatie\WebhookServer\WebhookServerServiceProvider"
    
  2. Configure the webhook server in config/webhook-server.php:

    'webhooks' => [
        'stripe' => [
            'secret' => env('STRIPE_WEBHOOK_SECRET'),
            'url' => '/stripe/webhook',
        ],
    ],
    
  3. Define a webhook route in routes/web.php:

    use Spatie\WebhookServer\WebhookHandler;
    
    Route::post('/stripe/webhook', WebhookHandler::class);
    
  4. Create a handler for Stripe events:

    php artisan make:webhook-handler StripeWebhookHandler
    
    // app/Handlers/Webhook/StripeWebhookHandler.php
    namespace App\Handlers\Webhook;
    
    use Spatie\WebhookServer\WebhookHandler as BaseHandler;
    use Spatie\WebhookServer\WebhookModels\WebhookCall;
    use Symfony\Component\HttpFoundation\Request;
    
    class StripeWebhookHandler extends BaseHandler
    {
        public function handle(Request $request): string
        {
            $payload = $request->getContent();
            $sigHeader = $request->headers->get('stripe-signature');
    
            // Validate signature (Symfony's Webhook component handles this under the hood)
            $this->validateWebhook($payload, $sigHeader);
    
            // Process the event
            $event = json_decode($payload, true);
            // Your logic here...
    
            return 'OK';
        }
    
        public function validateWebhook(string $payload, string $signature): void
        {
            // Use Symfony's Webhook component for validation
            $remoteEvent = $this->parseRemoteEvent($payload, $signature);
        }
    
        protected function parseRemoteEvent(string $payload, string $signature): void
        {
            $parser = new \Symfony\Component\Webhook\Parser\JsonParser();
            $remoteEvent = $parser->parse($payload, $signature, config('webhook-server.stripe.secret'));
        }
    }
    
  5. Register the handler in config/webhook-server.php:

    'webhooks' => [
        'stripe' => [
            'secret' => env('STRIPE_WEBHOOK_SECRET'),
            'url' => '/stripe/webhook',
            'handler' => \App\Handlers\Webhook\StripeWebhookHandler::class,
        ],
    ],
    
  6. Test locally with a tool like ngrok to expose your local server to Stripe’s webhook tester.


First Use Case: Stripe Webhook Integration

  1. Set up the webhook endpoint as shown above.
  2. Test with Stripe’s CLI:
    stripe listen --forward-to localhost:8000/stripe/webhook
    
  3. Handle events in your handler:
    public function handle(Request $request): string
    {
        $event = json_decode($request->getContent(), true);
    
        if ($event['type'] === 'payment_intent.succeeded') {
            // Dispatch a Laravel event or queue a job
            event(new PaymentSucceeded($event['data']['object']));
        }
    
        return 'OK';
    }
    

Key First Steps Summary

  1. Install the package and publish config.
  2. Define webhook routes and handlers.
  3. Validate signatures using Symfony’s JsonParser.
  4. Process events in your handler.
  5. Test with the provider’s webhook tester (e.g., Stripe CLI).

Implementation Patterns


Sending Webhooks (Outbound)

Use Symfony’s WebhookSender to send webhooks with retries and signing:

  1. Install the HTTP client:

    composer require symfony/http-client
    
  2. Create a sender service:

    // app/Services/WebhookSenderService.php
    namespace App\Services;
    
    use Symfony\Component\HttpClient\HttpClient;
    use Symfony\Component\Webhook\Sender\WebhookSender;
    use Symfony\Component\Webhook\Sender\WebhookSenderInterface;
    
    class WebhookSenderService
    {
        public function __construct(
            private WebhookSenderInterface $sender
        ) {}
    
        public function send(string $url, array $payload, string $secret): void
        {
            $this->sender->send($url, $payload, [
                'headers' => [
                    'Content-Type' => 'application/json',
                    'X-Signature' => $this->generateSignature($payload, $secret),
                ],
            ]);
        }
    
        private function generateSignature(array $payload, string $secret): string
        {
            return hash_hmac('sha256', json_encode($payload), $secret);
        }
    }
    
  3. Register the sender in a service provider:

    // app/Providers/WebhookServiceProvider.php
    namespace App\Providers;
    
    use Illuminate\Support\ServiceProvider;
    use Symfony\Component\HttpClient\HttpClient;
    use Symfony\Component\Webhook\Sender\WebhookSender;
    
    class WebhookServiceProvider extends ServiceProvider
    {
        public function register(): void
        {
            $this->app->singleton(WebhookSenderInterface::class, function () {
                $client = HttpClient::create();
                return new WebhookSender($client);
            });
        }
    }
    
  4. Use the sender in a job or controller:

    use App\Services\WebhookSenderService;
    
    class SendPaymentConfirmationJob implements ShouldQueue
    {
        public function handle(WebhookSenderService $sender): void
        {
            $sender->send(
                'https://example.com/webhook',
                ['event' => 'payment.confirmed', 'data' => $this->paymentData],
                config('services.stripe.webhook_secret')
            );
        }
    }
    

Receiving Webhooks (Inbound)

Leverage spatie/laravel-webhook-server as a facade over Symfony’s WebhookReceiver:

  1. Define a receiver:

    // app/Handlers/Webhook/SlackWebhookHandler.php
    namespace App\Handlers\Webhook;
    
    use Spatie\WebhookServer\WebhookHandler;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\Webhook\Parser\JsonParser;
    use Symfony\Component\Webhook\RemoteEvent;
    
    class SlackWebhookHandler extends WebhookHandler
    {
        public function handle(Request $request): string
        {
            $parser = new JsonParser();
            $remoteEvent = $parser->parse(
                $request->getContent(),
                $request->headers->get('X-Slack-Signature'),
                config('webhook-server.slack.secret')
            );
    
            // Process the event
            $this->processSlackEvent($remoteEvent);
    
            return 'OK';
        }
    
        private function processSlackEvent(RemoteEvent $event): void
        {
            $payload = json_decode($event->getPayload(), true);
            if ($payload['type'] === 'event_callback') {
                // Handle Slack event
            }
        }
    }
    
  2. Handle multiple events per payload (e.g., Stripe’s array of events):

    use Symfony\Component\Webhook\Parser\RequestParserInterface;
    
    class MultiEventParser implements RequestParserInterface
    {
        public function parse(Request $request): iterable
        {
            $payload = json_decode($request->getContent(), true);
            foreach ($payload['data'] as $event) {
                yield new RemoteEvent(
                    $event['id'],
                    $event['type'],
                    $event['object'],
                    $request->getContent()
                );
            }
        }
    }
    
  3. Register the parser in your handler:

    public function handle(Request $request): string
    {
        $parser = new MultiEventParser();
        foreach ($parser->parse($request) as $event) {
            $this->processEvent($event);
        }
        return 'OK';
    }
    

Queue-Based Processing

Offload webhook processing to Laravel queues for scalability:

  1. Dispatch a job in your handler:

    public function handle(Request $request): string
    {
        $event = $this->parseEvent($request);
        ProcessWebhookJob::dispatch($event);
    
        return 'OK';
    }
    
  2. Create a job:

    // app/Jobs/ProcessWebhookJob.php
    namespace App\Jobs;
    
    use Illuminate\Bus\Queueable;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Illuminate\Foundation\Bus\Dispatchable;
    use Symfony\Component\Webhook\RemoteEvent;
    
    class ProcessWebhookJob implements ShouldQueue
    {
        use Dispatchable, Queueable;
    
        public function __construct(
            private RemoteEvent $event
        ) {}
    
        public function handle(): void
        {
            // Process the event (e.g., update DB, send notifications)
        }
    }
    
  3. Configure queue workers:

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport