moneo / laravel-request-forwarder
Laravel Request Forwarder allows you to forward incoming requests to another addresses.
Fund package maintenance!
moneo
Installs: 391
Dependents: 0
Suggesters: 0
Security: 0
Stars: 62
Watchers: 3
Forks: 4
Open Issues: 0
pkg:composer/moneo/laravel-request-forwarder
Requires
- php: ^8.2
- illuminate/contracts: ^11.0|^12.0
- illuminate/http: ^11.0|^12.0
- illuminate/queue: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- nunomaduro/collision: ^8.0
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-arch: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
- phpstan/extension-installer: ^1.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- spatie/laravel-ray: ^1.26
This package is auto-updated.
Last update: 2026-02-16 12:34:17 UTC
README
Laravel Request Forwarder
Forward incoming HTTP requests to multiple destinations -- asynchronously, reliably, and with zero config overhead.
Some webhook providers only allow a single callback URL. This package sits behind that URL and fans the request out to as many targets as you need -- different servers, Slack, Discord, or any custom destination -- all processed through Laravel's queue system with automatic retries and failure logging.
Highlights
- Async by default -- Requests are dispatched to a queue job so your response time stays fast.
- Multi-target fan-out -- Forward a single incoming request to one or many endpoints in parallel.
- Custom providers -- Ship your own delivery logic by implementing a single interface. Discord provider included.
- Events on every delivery --
WebhookSentandWebhookFailedevents let you build dashboards, alerts, or audit logs. - Automatic retries -- Configurable
triesand exponentialbackoffper queue job, with failure logging out of the box.
Table of Contents
- Requirements
- Installation
- Quick Start
- Configuration Reference
- Usage
- Events
- Queue & Retry
- Error Handling & Logging
- Upgrade Guide (v1.x to v2.0)
- Testing
- Changelog
- Contributing
- Maintainers
- Security Vulnerabilities
- Credits
- License
Requirements
- PHP 8.2 or higher
- Laravel 11.x or 12.x
Installation
Install the package via Composer:
composer require moneo/laravel-request-forwarder
Publish the configuration file:
php artisan vendor:publish --tag="request-forwarder-config"
Quick Start
1. Set your target URL in .env:
REQUEST_FORWARDER_DEFAULT_URL=https://your-target.com/webhook
2. Add the middleware to any route you want to forward:
Route::middleware('request-forwarder') ->post('/webhook', fn () => response()->json(['status' => 'ok']));
That's it. Every request hitting /webhook is now forwarded to your target asynchronously via the queue.
Configuration Reference
After publishing, the config lives at config/request-forwarder.php:
return [ 'default_webhook_group_name' => 'default', 'webhooks' => [ 'default' => [ 'targets' => [ [ 'url' => env('REQUEST_FORWARDER_DEFAULT_URL', 'https://example.com/webhook'), 'method' => 'POST', ], ], ], ], 'timeout' => 30, 'queue_name' => env('REQUEST_FORWARDER_QUEUE', ''), 'queue_class' => Moneo\RequestForwarder\ProcessRequestForwarder::class, 'tries' => 3, 'backoff' => [5, 30, 60], 'log_failures' => true, ];
Key-by-key breakdown:
default_webhook_group_name-- Which webhook group to use when the middleware is called without a parameter.webhooks-- A map of named groups. Each group contains atargetsarray. Every target needs at least aurl. Optional keys per target:method(defaultPOST),provider,headers,timeout.timeout-- Global HTTP timeout in seconds for outgoing requests. Can be overridden per target.queue_name-- The queue connection/name for async jobs. Leave empty to use Laravel's default queue.queue_class-- The job class used for dispatching. Override this if you need custom job logic.tries-- How many times a failed job is retried before being marked as permanently failed.backoff-- Seconds to wait between retries. Accepts a single integer or an array for progressive backoff.log_failures-- Whentrue, failed deliveries are written to your Laravel log.
Usage
Middleware
Attach the request-forwarder middleware to any route. The middleware dispatches a queue job and lets the request continue normally -- your users see no delay.
// Forward using the default webhook group Route::middleware('request-forwarder') ->post('/webhook', fn () => 'OK'); // Forward using a named webhook group Route::middleware('request-forwarder:payments') ->post('/payments/webhook', fn () => 'OK');
Facade / Programmatic
Use the RequestForwarder facade when you need more control:
use Moneo\RequestForwarder\Facades\RequestForwarder; // Dispatch to queue (async) RequestForwarder::sendAsync($request); RequestForwarder::sendAsync($request, 'payments'); // Trigger immediately (sync) -- useful in jobs or artisan commands RequestForwarder::triggerHooks('https://original-url.com/hook', ['key' => 'value']); RequestForwarder::triggerHooks('https://original-url.com/hook', $data, 'payments');
Multiple Webhook Groups
Define as many groups as you need. Each group is independently configurable:
'webhooks' => [ 'default' => [ 'targets' => [ ['url' => 'https://primary-backend.com/hook', 'method' => 'POST'], ], ], 'payments' => [ 'targets' => [ ['url' => 'https://accounting.internal/stripe', 'method' => 'POST'], [ 'url' => 'https://discord.com/api/webhooks/...', 'method' => 'POST', 'provider' => \Moneo\RequestForwarder\Providers\Discord::class, ], ], ], ],
Per-Target Headers
Add authentication or custom headers to individual targets:
'targets' => [ [ 'url' => 'https://api.partner.com/webhook', 'method' => 'POST', 'headers' => [ 'Authorization' => 'Bearer your-api-token', 'X-Webhook-Secret' => 'shared-secret', ], ], ],
Per-Target Timeout
Override the global timeout for slow endpoints:
'targets' => [ [ 'url' => 'https://slow-service.example.com/hook', 'method' => 'POST', 'timeout' => 60, ], ],
Custom Providers
The default provider sends JSON over HTTP. Need a different format? Implement ProviderInterface:
use Illuminate\Http\Client\Factory; use Illuminate\Http\Client\Response; use Moneo\RequestForwarder\Providers\ProviderInterface; class SlackProvider implements ProviderInterface { public function __construct(private readonly Factory $client) { } public function send(string $url, array $params, array $webhook): Response { return $this->client ->timeout($webhook['timeout'] ?? 30) ->send('POST', $webhook['url'], [ 'json' => [ 'text' => "Webhook from {$url}\n```" . json_encode($params, JSON_PRETTY_PRINT) . '```', ], ]); } }
Register your provider in the target config:
'targets' => [ [ 'url' => 'https://hooks.slack.com/services/T.../B.../xxx', 'method' => 'POST', 'provider' => App\Webhooks\SlackProvider::class, ], ],
The package validates that every provider class exists and implements ProviderInterface before instantiation. Providers are resolved through Laravel's container, so constructor injection works out of the box.
Events
Every delivery attempt dispatches an event you can hook into:
WebhookSent -- Dispatched after a successful HTTP response (any status code).
string $sourceUrl-- The original incoming request URL.string $targetUrl-- The target the request was forwarded to.int $statusCode-- The HTTP status code returned by the target.
WebhookFailed -- Dispatched when the delivery throws any exception.
string $sourceUrl-- The original incoming request URL.string $targetUrl-- The target that failed.\Throwable $exception-- The exception that was caught.
Example listener:
use Moneo\RequestForwarder\Events\WebhookSent; use Moneo\RequestForwarder\Events\WebhookFailed; // In a service provider or event subscriber Event::listen(WebhookSent::class, function (WebhookSent $event) { logger()->info("Forwarded to {$event->targetUrl}", [ 'status' => $event->statusCode, ]); }); Event::listen(WebhookFailed::class, function (WebhookFailed $event) { logger()->error("Forward to {$event->targetUrl} failed", [ 'error' => $event->exception->getMessage(), ]); });
Queue & Retry
The middleware dispatches a ProcessRequestForwarder job. Configure its behavior in the config:
'queue_name' => env('REQUEST_FORWARDER_QUEUE', 'webhooks'), 'tries' => 3, 'backoff' => [5, 30, 60], // wait 5s, then 30s, then 60s
- If
queue_nameis empty, Laravel's default queue is used. triesandbackoffare validated at runtime; invalid values fall back to safe defaults (3tries,[5, 30, 60]backoff).
Custom job class: If you need to customize serialization, middleware, or tagging, extend the default job and point the config to your class:
// app/Jobs/CustomForwarder.php class CustomForwarder extends \Moneo\RequestForwarder\ProcessRequestForwarder { public $timeout = 120; public function tags(): array { return ['webhook-forwarder', "group:{$this->webhookName}"]; } }
// config/request-forwarder.php 'queue_class' => App\Jobs\CustomForwarder::class,
Error Handling & Logging
The package is designed to never break your application flow:
-
Inside
triggerHooks: Each target is processed independently. If one target fails, the remaining targets still execute. Failed targets dispatch aWebhookFailedevent and (whenlog_failuresistrue) write an error to your Laravel log. -
Queue job failures: When all retries are exhausted, the job's
failed()method logs the final error with the source URL and webhook group name. -
Strict validation: Invalid URLs, unsupported HTTP methods, non-positive timeouts, non-existent provider classes, and malformed config shapes all throw
InvalidArgumentExceptioneagerly -- so misconfigurations surface during development, not in production.
Upgrade Guide (v1.x to v2.0)
Runtime requirements
- PHP
8.2+(was8.1+). - Laravel
11.xor12.x(Laravel 10 support dropped; it reached EOL in February 2025).
Config validation is now strict
v2.0 fails fast on invalid configuration instead of silently ignoring it. Check your webhook groups:
targetsmust be a non-empty array.- Each target must have a valid
url(passesFILTER_VALIDATE_URL). methodmust be one of:GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS.timeoutmust be a positive number.headersmust be an array of string keys with scalar values.
Provider validation
- Custom providers must exist as classes and implement
ProviderInterface. - Invalid providers now trigger
WebhookFailedinstead of being silently skipped.
Queue behavior
- Empty
queue_nameno longer forces an empty string -- it falls back to Laravel's default queue. triesandbackoffare validated and normalized at runtime.
Event typing
WebhookFailed::$exceptionis now\Throwable(was\Exception).
Recommended steps
- Update your dependency:
composer require moneo/laravel-request-forwarder:^2.0 - Re-publish the config:
php artisan vendor:publish --tag="request-forwarder-config" --force - Review your webhook groups against the validation rules above.
- Run your test suite and verify webhook flows in staging.
- Monitor logs for any
InvalidArgumentExceptionentries from the package.
Testing
# Run the test suite composer test # Run static analysis composer analyse # Fix code style composer format
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.
