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

Laravel Stripe Webhooks Laravel Package

spatie/laravel-stripe-webhooks

Laravel package to handle Stripe webhooks: verifies Stripe signatures, stores valid webhook calls in the database, and dispatches configurable jobs or events per Stripe event type. You implement the business logic (payments, subscriptions, etc.).

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation

    composer require spatie/laravel-stripe-webhooks
    php artisan vendor:publish --provider="Spatie\StripeWebhooks\StripeWebhooksServiceProvider"
    php artisan vendor:publish --provider="Spatie\WebhookClient\WebhookClientServiceProvider" --tag="webhook-client-migrations"
    php artisan migrate
    
  2. Configure Stripe Webhook Endpoint Add to routes/web.php:

    Route::stripeWebhooks('stripe/webhook');
    

    Exclude this route from CSRF middleware in app/Http/Middleware/VerifyCsrfToken.php:

    protected $except = [
        'stripe/webhook',
    ];
    
  3. Set Up Signing Secret In .env:

    STRIPE_WEBHOOK_SECRET=whsec_...
    

    In config/stripe-webhooks.php:

    'signing_secret' => env('STRIPE_WEBHOOK_SECRET'),
    
  4. First Use Case: Handle a Payment Intent Succeeded Event Create a job:

    php artisan make:job HandlePaymentIntentSucceeded
    

    Update config/stripe-webhooks.php:

    'jobs' => [
        'payment_intent.succeeded' => \App\Jobs\StripeWebhooks\HandlePaymentIntentSucceeded::class,
    ],
    

Implementation Patterns

Workflow: Handling Webhooks with Jobs

  1. Define Job

    namespace App\Jobs\StripeWebhooks;
    
    use Illuminate\Bus\Queueable;
    use Spatie\WebhookClient\Models\WebhookCall;
    
    class HandlePaymentIntentSucceeded
    {
        use Queueable;
    
        public function __construct(public WebhookCall $webhookCall) {}
    
        public function handle()
        {
            $payload = $this->webhookCall->payload;
            $paymentIntent = \Stripe\PaymentIntent::retrieve($payload['data']['object']['id']);
            // Process payment intent logic
        }
    }
    
  2. Register Job in Config

    'jobs' => [
        'payment_intent.succeeded' => \App\Jobs\StripeWebhooks\HandlePaymentIntentSucceeded::class,
    ],
    
  3. Queue Configuration In .env:

    STRIPE_WEBHOOK_CONNECTION=redis
    STRIPE_WEBHOOK_QUEUE=stripe
    

Workflow: Handling Webhooks with Events

  1. Create Listener

    php artisan make:listener HandleStripeWebhook
    
    namespace App\Listeners;
    
    use Spatie\WebhookClient\Models\WebhookCall;
    
    class HandleStripeWebhook
    {
        public function handle(WebhookCall $webhookCall)
        {
            $event = \Stripe\Event::constructFrom(
                json_decode($webhookCall->payload, true)
            );
            // Handle event logic
        }
    }
    
  2. Register Listener in EventServiceProvider

    protected $listen = [
        'stripe-webhooks::payment_intent.succeeded' => [
            \App\Listeners\HandleStripeWebhook::class,
        ],
    ];
    

Integration Tips

  • Logging: Use Laravel's logging to track webhook processing:
    \Log::info('PaymentIntent succeeded', ['payload' => $payload]);
    
  • Testing: Mock Stripe webhooks using Spatie\WebhookClient\WebhookCall factory:
    $webhookCall = WebhookCall::factory()->create([
        'payload' => json_encode(['data' => ['object' => ['id' => 'pi_123']]]),
    ]);
    
  • Idempotency: Ensure duplicate handling by checking webhook_calls table:
    if (!WebhookCall::where('payload->id', $payload['id'])->exists()) {
        // Process
    }
    

Gotchas and Tips

Pitfalls

  1. Signature Verification in Local Environments Disable verification in .env for local testing:

    STRIPE_SIGNATURE_VERIFY=false
    

    Re-enable in production:

    STRIPE_SIGNATURE_VERIFY=true
    
  2. Queue Failures Monitor failed jobs in Laravel Horizon or failed_jobs table. Retry manually:

    dispatch(new \Spatie\StripeWebhooks\ProcessStripeWebhookJob(WebhookCall::find($id)));
    
  3. Duplicate Events Stripe may send duplicates. Use webhook_calls table to deduplicate:

    if (WebhookCall::where('payload->id', $payload['id'])->exists()) {
        return;
    }
    
  4. Payload Access Ensure payload is decoded correctly:

    $payload = json_decode($webhookCall->payload, true);
    $event = \Stripe\Event::constructFrom($payload);
    

Debugging

  • Check Webhook Logs: Query webhook_calls table for failed events:
    SELECT * FROM webhook_calls WHERE exception IS NOT NULL;
    
  • Stripe CLI: Test webhooks locally using Stripe CLI:
    stripe listen --forward-to localhost/stripe/webhook
    
  • Event Types: Verify event names match Stripe’s event types. Replace . with _ in config keys.

Extension Points

  1. Custom Job Handling Extend ProcessStripeWebhookJob for pre/post-processing:

    namespace App\Jobs;
    
    use Spatie\StripeWebhooks\ProcessStripeWebhookJob;
    
    class CustomStripeWebhookJob extends ProcessStripeWebhookJob
    {
        public function handle()
        {
            \Log::info('Custom logic before parent handle');
            parent::handle();
            \Log::info('Custom logic after parent handle');
        }
    }
    

    Update config:

    'model' => \App\Jobs\CustomStripeWebhookJob::class,
    
  2. Dynamic Secret Handling For Stripe Connect, use route parameters:

    Route::stripeWebhooks('stripe/webhook/{configKey}');
    

    Configure secrets in stripe-webhooks.php:

    'signing_secret_connect' => env('STRIPE_WEBHOOK_SECRET_CONNECT'),
    
  3. Payload Transformation Use Stripe\Event::constructFrom() for type-safe access:

    $event = \Stripe\Event::constructFrom($payload);
    $paymentIntent = $event->data->object;
    

Tips

  • Rate Limiting: Stripe may throttle requests. Use queue workers to handle bursts.
  • Environment Variables: Store secrets in .env and use env() in config:
    'signing_secret' => env('STRIPE_WEBHOOK_SECRET'),
    
  • Testing: Use stripe/webhook-testing endpoint to simulate events:
    curl -X POST https://api.stripe.com/v1/webhook_endpoints/test_constructed_webhook \
         -u sk_test_...
    
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