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 Endpoint:

    • Add your Stripe webhook secret to .env (STRIPE_WEBHOOK_SECRET).
    • Define the webhook route in routes/web.php:
      Route::stripeWebhooks('stripe/webhook');
      
    • Exclude the route from CSRF verification in app/Http/Middleware/VerifyCsrfToken.php:
      protected $except = [
          'stripe/webhook',
      ];
      
  3. First Use Case:

    • Define a job for a specific Stripe event (e.g., charge.succeeded) in config/stripe-webhooks.php:
      'jobs' => [
          'charge_succeeded' => \App\Jobs\StripeWebhooks\HandleChargeSucceeded::class,
      ],
      
    • Create the job:
      namespace App\Jobs\StripeWebhooks;
      
      use Illuminate\Bus\Queueable;
      use Illuminate\Queue\SerializesModels;
      use Illuminate\Queue\InteractsWithQueue;
      use Spatie\WebhookClient\Models\WebhookCall;
      
      class HandleChargeSucceeded
      {
          use InteractsWithQueue, Queueable, SerializesModels;
      
          public $webhookCall;
      
          public function __construct(WebhookCall $webhookCall)
          {
              $this->webhookCall = $webhookCall;
          }
      
          public function handle()
          {
              $payload = $this->webhookCall->payload;
              // Handle charge.succeeded logic here
          }
      }
      

Implementation Patterns

Usage Patterns

  1. Job-Based Handling:

    • Define jobs for specific Stripe events in config/stripe-webhooks.php.
    • Use WebhookCall to access the raw payload and metadata (e.g., id, created_at).
    • Example:
      public function handle(WebhookCall $webhookCall)
      {
          $event = $webhookCall->payload['data']['object'];
          // Process $event (e.g., update user subscription)
      }
      
  2. Event-Based Handling:

    • Listen to stripe-webhooks::<event_name> events in EventServiceProvider:
      protected $listen = [
          'stripe-webhooks::charge.succeeded' => [
              \App\Listeners\HandleChargeSucceeded::class,
          ],
      ];
      
    • Use ShouldQueue for async processing:
      class HandleChargeSucceeded implements ShouldQueue
      {
          public function handle(WebhookCall $webhookCall) { ... }
      }
      
  3. Default Job Handling:

    • Set a default_job in config to handle unregistered events:
      'default_job' => \App\Jobs\StripeWebhooks\HandleDefaultEvent::class,
      
  4. Payload Transformation:

    • Convert payload to Stripe objects for easier access:
      use Stripe\Event;
      
      public function handle(WebhookCall $webhookCall)
      {
          $stripeEvent = Event::constructFrom($webhookCall->payload);
          $charge = $stripeEvent->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. Retrying Failed Webhooks:

    • Manually reprocess a failed webhook:
      use Spatie\StripeWebhooks\ProcessStripeWebhookJob;
      
      ProcessStripeWebhookJob::dispatch(WebhookCall::find($id));
      
  3. Multi-Tenant/Connect Support:

    • Route webhooks by secret using dynamic config keys:
      Route::stripeWebhooks('stripe/webhook/{secretKey}');
      
    • Configure secrets in config/stripe-webhooks.php:
      'signing_secret_connect' => env('STRIPE_CONNECT_WEBHOOK_SECRET'),
      

Integration Tips

  • Logging: Use Laravel’s logging to track webhook processing:
    \Log::info('Processed webhook', ['event' => $webhookCall->payload['type']]);
    
  • Validation: Validate payloads using Stripe’s SDK:
    try {
        \Stripe\Webhook::constructEvent(
            $webhookCall->payload,
            $webhookCall->signature,
            env('STRIPE_WEBHOOK_SECRET')
        );
    } catch (\Stripe\Exception\SignatureVerificationException $e) {
        \Log::error('Webhook signature verification failed', ['error' => $e->getMessage()]);
    }
    
  • Queue Management: Monitor failed jobs in Laravel Horizon or queues:
    php artisan queue:work --queue=stripe-webhook-queue
    

Gotchas and Tips

Pitfalls

  1. Signature Mismatches:

    • Issue: Invalid STRIPE_WEBHOOK_SECRET or missing Stripe-Signature header.
    • Fix: Verify the secret in Stripe Dashboard and ensure headers are forwarded (e.g., in Nginx/Apache).
    • Debug: Check webhook_calls table for failed entries with exception column populated.
  2. Duplicate Events:

    • Issue: Stripe may resend events. The package deduplicates by default, but ensure your logic handles idempotency.
    • Fix: Use WebhookCall::where('payload->id', $eventId)->exists() to check for duplicates.
  3. Queue Timeouts:

    • Issue: Long-running jobs may timeout (default: 60s for Laravel queues).
    • Fix: Use ShouldQueue and optimize job logic. Increase timeout in queue.php:
      'timeout' => 300, // 5 minutes
      
  4. Payload Size Limits:

    • Issue: Large payloads (e.g., customer.created with many metadata) may exceed PHP limits.
    • Fix: Increase post_max_size and memory_limit in php.ini or stream payloads.
  5. Route Misconfiguration:

    • Issue: Webhook route not excluded from CSRF or not matching Stripe’s endpoint.
    • Fix: Double-check VerifyCsrfToken middleware and Stripe Dashboard URL.

Debugging

  • Log Webhook Payloads:
    \Log::debug('Webhook payload', $webhookCall->payload);
    
  • Inspect Database:
    php artisan tinker
    >>> \Spatie\WebhookClient\Models\WebhookCall::latest()->first();
    
  • Stripe CLI Testing:
    stripe listen --forward-to localhost/stripe/webhook --print-payload-only
    

Config Quirks

  1. Dynamic Config Keys:

    • For multi-secret setups, ensure config keys follow the pattern signing_secret_{key} (e.g., signing_secret_connect).
  2. Default Job:

    • If default_job is empty, events are stored but not processed. Set a job to handle them:
      'default_job' => \App\Jobs\StripeWebhooks\LogUnmappedEvents::class,
      
  3. Model Customization:

    • Extend ProcessStripeWebhookJob for pre/post-processing:
      class CustomWebhookJob extends \Spatie\StripeWebhooks\ProcessStripeWebhookJob
      {
          public function handle()
          {
              \Log::info('Custom logic before parent');
              parent::handle();
              \Log::info('Custom logic after parent');
          }
      }
      

Extension Points

  1. Custom Profiles:

    • Implement WebhookProfile to filter webhooks dynamically:
      class CustomProfile implements \Spatie\WebhookClient\WebhookProfile\WebhookProfile
      {
          public function shouldProcess(Request $request): bool
          {
              return $request->ip() === 'trusted-ip';
          }
      }
      
    • Set in config:
      'profile' => \App\Profiles\CustomProfile::class,
      
  2. Middleware:

    • Add middleware to the webhook route for pre-processing:
      Route::stripeWebhooks('stripe/webhook')
           ->middleware(\App\Http\Middleware\ValidateWebhookData::class);
      
  3. Webhook Call Model:

    • Extend WebhookCall to add custom fields:
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.
codraw/framework-extra-bundle
codraw/messenger
codraw/security
codraw/mailer
codraw/contracts
codraw/profiling
codraw/dependency-injection
codraw/tester
codraw/core
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony