strucura / webhooks
Lightweight utilities for dispatching and managing webhooks from your application. This package provides a small Connector abstraction, a WebhookProcessor contract for building outgoing requests, and job wiring that records attempts and schedules retries.
Fund package maintenance!
Strucura
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/strucura/webhooks
Requires
- php: ^8.3
- illuminate/contracts: ^11.0||^12.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- spatie/laravel-ray: ^1.35
This package is auto-updated.
Last update: 2026-02-16 15:04:51 UTC
README
Caution
This is not ready for production usage, use at your own risk.
Lightweight utilities for dispatching and managing webhooks from your application. This package provides a small Connector abstraction, a WebhookProcessor contract for building outgoing requests, and job wiring that records attempts and schedules retries.
Installation
You can install the package via composer:
composer require strucura/webhooks
You can publish and run the migrations with:
php artisan vendor:publish --tag="webhooks-migrations" php artisan vendor:publish --tag="webhooks-config" php artisan migrate
Quick overview
- Connector: encapsulates base URL, default headers and client options. Optionally implements rate limiting to protect external APIs.
- WebhookProcessor: responsible for producing the request data and choosing the Connector to use.
- ProcessWebhookJob: invokes the processor, logs results, saves attempts, and schedules retries when appropriate. Automatically handles rate limiting via middleware.
Configuration Example
use Strucura\Webhooks\BackoffStrategies\ExponentialBackoff; use App\Webhooks\Events\OrderCreatedEvent; use App\Webhooks\Processors\OrderCreatedProcessor; return [ 'defaults' => [ 'max_attempts' => 5, 'queue' => 'webhooks', 'backoff_strategy' => new ExponentialBackoff(2, 600), ], 'events' => [ OrderCreatedEvent::class => [ 'processor' => OrderCreatedProcessor::class, 'max_attempts' => 10, // Override default 'backoff_strategy' => new JitteredBackoff(60, 30), // Override default ], ], ];
Backoff Strategies
Backoff strategies determine the delay between webhook retry attempts. Configure them in your config/webhooks.php file.
| Strategy | Description | Parameters | Example Usage |
|---|---|---|---|
StaticIntervalBackoff |
Fixed delay between all retries | $waitTimeInSeconds (int) |
new StaticIntervalBackoff(60) - waits 60 seconds between each retry |
ExponentialBackoff |
Exponentially increasing delay (2^n) | $baseWaitTime (int, default: 1)$maxWaitTime (int, default: 60) |
new ExponentialBackoff(1, 300) - starts at 1s, doubles each time, caps at 300s |
LinearBackoff |
Linearly increasing delay | $baseWaitTime (int, default: 1) |
new LinearBackoff(30) - waits 30s, 60s, 90s, 120s... |
JitteredBackoff |
Linear delay with random jitter to prevent thundering herd | $baseWaitTime (int, default: 1)$maxJitter (int, default: 5) |
new JitteredBackoff(30, 10) - waits 30s + 0-10s random, then 60s + 0-10s random... |
Artisan Commands
The package includes several artisan commands for managing webhooks.
| Command | Description | Options | Example |
|---|---|---|---|
webhooks:retry |
Retry failed webhooks that haven't reached max attempts | --all - Retry all incomplete webhooks regardless of attempt count |
php artisan webhooks:retryphp artisan webhooks:retry --all |
webhooks:increase-attempts |
Interactively increase max attempts for a specific webhook | None (interactive prompts) | php artisan webhooks:increase-attempts |
make:connector |
Generate a new Connector class | Standard make command options | php artisan make:connector StripeConnector |
make:webhook-processor |
Generate a new WebhookProcessor class | Standard make command options | php artisan make:webhook-processor StripeChargeProcessor |
Rate Limiting
Protect external APIs from being overwhelmed by implementing rate limiting on your connectors. Rate limiting prevents your application from exceeding API rate limits and receiving 429 errors.
How It Works
- Job Middleware: Rate limiting is implemented via job middleware that checks before webhook execution
- Automatic Retry: When rate limited, jobs are released back to the queue with an appropriate delay
- Doesn't Consume Attempts: Throttled jobs don't count toward max retry attempts
- Per-Connector Configuration: Each connector can define its own rate limits
Basic Usage
Implement the HasRateLimit contract on any connector to enable rate limiting:
<?php namespace App\Http\Integrations; use Strucura\Webhooks\Abstracts\Connector; use Strucura\Webhooks\Contracts\HasRateLimit; class StripeConnector extends Connector implements HasRateLimit { protected function resolveBaseUrl(): string { return 'https://api.stripe.com'; } public function rateLimitKey(): string { // Unique key for this connector's rate limit return 'webhook:connector:stripe'; } public function rateLimitMaxAttempts(): int { // Stripe allows 100 requests per second return 100; } public function rateLimitDecaySeconds(): int { // Time window in seconds return 1; // per second } }
Configuration Methods
| Method | Return Type | Description |
|---|---|---|
rateLimitKey() |
string |
Unique identifier for this rate limit. Used to track requests in Laravel's RateLimiter. |
rateLimitMaxAttempts() |
int |
Maximum number of webhook jobs allowed within the time window. |
rateLimitDecaySeconds() |
int |
Time window in seconds. After this period, the counter resets. |
Behavior When Rate Limited
When a webhook job exceeds the rate limit:
- The job is released back to the queue (not failed)
- Release delay equals the time until the rate limit window resets
- The job will be retried automatically when the window resets
- Webhook attempts counter is not incremented (throttling doesn't count as a failure)
- Jobs have a 24-hour retry window to allow sufficient time for throttling delays
Testing
Prefer Laravel's HTTP testing helpers for deterministic tests. Example:
use Illuminate\Support\Facades\Http; Http::fake([ 'https://api.example.test/*' => Http::response(['ok' => true], 200), ]); // Run code that triggers the request (e.g. ProcessWebhookJob) Http::assertSent(function ($request) { return $request->url() === 'https://api.example.test/hook' && $request->method() === 'POST' && $request['id'] === 1; });
Examples
Stripe connector & processor example
Below is an example of how you might implement a Stripe connector and a
processor that uses it to create a charge. This is documentation only — the
actual classes are not required in this repository. Implement them in your
application under App/Http/Integrations or a similar location.
StripeConnector (example)
<?php namespace App\Http\Integrations; use Strucura\Webhooks\Abstracts\Connector; use Strucura\Webhooks\Contracts\HasRateLimit; class StripeConnector extends Connector implements HasRateLimit { public function __construct(protected string $apiKey = '') { parent::__construct(); if ($apiKey !== '') { $this->addHeader('Authorization', 'Bearer ' . $apiKey); } } protected function resolveBaseUrl(): string { return 'https://api.stripe.com'; } protected function defaultHeaders(): array { return [ 'Accept' => 'application/json', ]; } protected function defaultConfig(): array { return [ 'timeout' => 30, ]; } // Rate limiting: Stripe allows 100 requests per second public function rateLimitKey(): string { return 'webhook:connector:stripe'; } public function rateLimitMaxAttempts(): int { return 100; } public function rateLimitDecaySeconds(): int { return 1; // per second } }
StripeChargeProcessor (example)
<?php namespace App\Http\Integrations; use Strucura\Webhooks\Abstracts\WebhookEvent; use Strucura\Webhooks\Abstracts\WebhookProcessor; use Strucura\Webhooks\Enums\HttpMethod; use Strucura\Webhooks\Connectors\StripeConnector; use Strucura\Webhooks\Models\Webhook; class StripeChargeProcessor extends WebhookProcessor { public function canDispatch(WebhookEvent $event): bool { return true; } public function canRetry(Webhook $webhook): bool { return true; } public function requestBody(): array { return [ 'amount' => 1000, 'currency' => 'usd', 'source' => 'tok_visa', 'description' => 'Example charge', ]; } public function connector(): StripeConnector { $apiKey = config('services.stripe.secret', ''); return new StripeConnector($apiKey); } protected function requestMethod(): HttpMethod { return HttpMethod::POST; } protected function requestEndpoint(): string { return '/v1/charges'; } protected function requestHeaders(): array { return [ 'Content-Type' => 'application/x-www-form-urlencoded', ]; } }
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.