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, logs valid calls to the database, and dispatches configurable jobs or events per webhook type. Provides the plumbing for receiving and validating webhooks; you implement the business logic.

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 Secret Add your Stripe webhook secret to .env:

    STRIPE_WEBHOOK_SECRET=whsec_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    
  3. Define Route In routes/web.php:

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

    Add the route to $except in app/Http/Middleware/VerifyCsrfToken.php:

    protected $except = [
        'stripe/webhook',
    ];
    
  4. First Use Case Create a job for handling a specific Stripe event (e.g., charge.succeeded):

    php artisan make:job HandleStripeChargeSucceeded
    

    Update config/stripe-webhooks.php:

    'jobs' => [
        'charge_succeeded' => \App\Jobs\HandleStripeChargeSucceeded::class,
    ],
    

Implementation Patterns

Usage Patterns

  1. Job-Based Handling

    • Define jobs in config/stripe-webhooks.php with Stripe event names (replace . with _).
    • Example job structure:
      public function handle(WebhookCall $webhookCall) {
          $payload = json_decode($webhookCall->payload, true);
          // Process payload (e.g., update user subscription)
      }
      
  2. Event-Based Handling

    • Register listeners in EventServiceProvider:
      protected $listen = [
          'stripe-webhooks::charge.succeeded' => [
              \App\Listeners\HandleChargeSucceeded::class,
          ],
      ];
      
    • Use WebhookCall payload in listeners:
      public function handle(WebhookCall $webhookCall) {
          $event = Event::constructFrom($webhookCall->payload);
          // Access Stripe objects (e.g., $event->data->object)
      }
      
  3. Queue Integration

    • Ensure jobs/listeners implement ShouldQueue to avoid timeouts:
      use Illuminate\Contracts\Queue\ShouldQueue;
      class HandleChargeSucceeded implements ShouldQueue { ... }
      
    • Configure queue in config/stripe-webhooks.php:
      'queue' => 'stripe-webhooks',
      
  4. Payload Transformation

    • Convert raw payload to Stripe objects:
      $event = Event::constructFrom($webhookCall->payload);
      $charge = $event->data->object; // Stripe\Charge
      

Workflows

  1. Testing Locally

    • Disable signature verification in .env:
      STRIPE_SIGNATURE_VERIFY=false
      
    • Use Stripe CLI to simulate webhooks:
      stripe listen --forward-to localhost/stripe/webhook
      
  2. Handling Duplicates

    • Leverage the webhook_calls table to deduplicate:
      if (!WebhookCall::where('payload->id', $payload['id'])->exists()) {
          // Process unique event
      }
      
  3. Retrying Failed Webhooks

    • Manually reprocess failed calls:
      dispatch(new ProcessStripeWebhookJob(WebhookCall::find($id)));
      

Gotchas and Tips

Pitfalls

  1. Signature Verification

    • Issue: Forgetting to add the webhook route to VerifyCsrfToken::$except causes 419 errors.
    • Fix: Always exclude the webhook route from CSRF protection.
  2. Queue Timeouts

    • Issue: Long-running jobs may timeout if not queued.
    • Fix: Use ShouldQueue and monitor queue workers.
  3. Duplicate Events

    • Issue: Stripe may resend events; unhandled duplicates can cause data issues.
    • Fix: Check webhook_calls table for existing payload IDs before processing.
  4. Environment Config

    • Issue: Hardcoding secrets in config files instead of .env.
    • Fix: Use env() in config/stripe-webhooks.php:
      'signing_secret' => env('STRIPE_WEBHOOK_SECRET'),
      

Debugging

  1. Log Webhook Payloads

    • Enable logging in StripeWebhookProfile:
      public function shouldProcess(Request $request): bool {
          \Log::info('Webhook payload:', $request->getContent());
          return true;
      }
      
  2. Inspect Database

    • Check webhook_calls for failed events:
      SELECT * FROM webhook_calls WHERE exception IS NOT NULL;
      
  3. Stripe CLI Testing

    • Use stripe listen --forward-to to test locally without deploying.

Extension Points

  1. Custom Models

    • Extend ProcessStripeWebhookJob for pre/post-processing:
      class CustomWebhookJob extends ProcessStripeWebhookJob {
          public function handle() {
              \Log::info('Custom logic before parent');
              parent::handle();
              \Log::info('Custom logic after parent');
          }
      }
      
    • Update config:
      'model' => \App\Jobs\CustomWebhookJob::class,
      
  2. Dynamic Secret Handling

    • Use route parameters for multiple secrets (e.g., Stripe Connect):
      Route::stripeWebhooks('stripe/webhook/{configKey}');
      
    • Configure secrets in config/stripe-webhooks.php:
      'signing_secret_connect' => env('STRIPE_CONNECT_SECRET'),
      
  3. Webhook Profiling

    • Implement WebhookProfile to filter requests:
      class CustomProfile implements WebhookProfile {
          public function shouldProcess(Request $request): bool {
              return $request->ip() === 'trusted-ip';
          }
      }
      
    • Update config:
      'profile' => \App\Profiles\CustomProfile::class,
      

Pro Tips

  • Idempotency: Use Stripe’s idempotency keys for critical operations.
  • Monitoring: Set up alerts for failed webhooks using Laravel Horizon or a queue monitor.
  • Payload Validation: Validate payload structure in jobs/listeners:
    if (!isset($payload['data']['object'])) {
        throw new \Exception('Invalid payload structure');
    }
    
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.
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope
anil/file-picker
broqit/fields-ai