Download the PHP package crumbls/fanout without Composer

On this page you can find all versions of the php package crumbls/fanout. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package fanout

crumbls/fanout

tests Latest Version

Catch incoming webhooks and fan them out to multiple downstream destinations — staging, dev, secondary services — with retries, signing, transformation, filtering, rate limiting, and replay.

Solves the "production webhooks never reach staging/dev" problem and the broader "I need to mirror webhooks across environments without writing a custom forwarder per source" problem.

Requirements

Install

fanout:install publishes config/fanout.php and the two migrations into your app.

Two ways to use it

Pattern A — Tee from your existing handler

Your remote service keeps pointing at your existing prod webhook URL. Your handler runs as it always has, then fires off the mirror in one line:

Two new lines, your prod handler stays exactly as it is. Use this when you already have working webhook handlers and just want them mirrored.

Pattern B — Make fanout the receiver

Point the remote service at https://prod.example.com/fanout/in/{profile}. Configure your prod handler URL as one of the endpoints alongside staging/dev:

Zero touches to existing app code, full audit trail of every hop. Trade-off: your prod handler now runs through the queue, adding a few hundred ms.

Concepts

Configuration

The remote service then sends webhooks to https://your-app.test/fanout/in/stripe-prod.

Persist modes

Per-profile setting that controls how much of an inbound event is stored.

Mode Event row Payload column Delivery rows Replayable Use when
full (default) yes encrypted, full body yes yes You want full audit + replay
metadata yes null yes no You need a timeline / response codes but the body is too sensitive to keep
none no n/a no no Pure forwarder — no DB writes

In none mode the receiver dispatches ephemeral delivery jobs that carry the payload in the job constructor; failures land in Laravel's failed_jobs table.

Encryption at rest

payload, headers, request_payload, request_headers, and last_response_body are all cast as Laravel encrypted / encrypted:array. Encryption key is your app APP_KEY.

If you need a different strategy — envelope encryption, per-tenant keys, KMS — extend the model and override the casts:

Then point the package at it:

Validators (inbound)

Optional. If unconfigured, the receiver accepts any caller — only do that for trusted internal sources.

Built-in:

Bring your own by implementing Crumbls\Fanout\Contracts\SignatureValidator:

Reference it from config:

Signers (outbound)

Per endpoint. Built-in:

Implement Crumbls\Fanout\Contracts\SignatureSigner for custom schemes (e.g. JWT, Ed25519, or a vendor-specific scheme).

Filters & transformers

Per endpoint, accepting class strings (closures can't live in cached config — register them at runtime via the manager if you need that).

Then in config:

Header templating

Endpoint headers support these tokens:

Retries

Per endpoint:

Each attempt is its own queue job. Failed deliveries stay in fanout_deliveries with status = failed so they're easy to find and replay.

Backoff Delay between attempts (base = 5s)
fixed 5, 5, 5, 5
linear 5, 10, 15, 20
exponential 5, 10, 20, 40

Rate limiting

Per endpoint:

When the limit is hit, the delivery is rescheduled with the resume time provided by Laravel's RateLimiter — without consuming a retry attempt.

Replay

Programmatic equivalents:

Programmatic dispatch

Inject events into the pipeline as if a webhook had arrived (no signature check, since the call is internal):

Pruning

Schedule it in routes/console.php:

Retention windows (pruning.keep_events_days, pruning.keep_failed_events_days) are baked onto each row's purgeable_at at write time, so pruning is a single indexed range delete.

Worker

Run a dedicated worker on the fanout queue:

Horizon is supported out of the box.

Recipes

"I want staging to receive everything, but only flag dev when I'm actively debugging"

Flip FANOUT_DEV_ENABLED=true only when you're working on something locally; flip it off when you're done.

"I want my dev tunnel to receive only the events I'm actively debugging"

Combine an environment variable with a per-endpoint filter:

Bind it in your service provider so you can pass the array from env:

"I want to strip PII before sending to dev/staging"

Use a PayloadTransformer (see the Filters & transformers section above).

"Staging and dev verify their own HMAC; how do I sign with each one's secret?"

Configure HmacSha256Signer per endpoint with that endpoint's own secret:

"Dev/staging verify against the original sender's secret"

Use PassthroughSigner — it forwards the original signature header verbatim. Only valid if you don't transform the payload (any byte change invalidates the signature).

"I want to fire a test webhook into the pipeline from a tinker session"

Returns a FanoutEvent you can then replay() or inspect.

"I want to send the same payload elsewhere on demand later"

Find the event id in fanout_events, then:

Or programmatically:

Troubleshooting

POST /fanout/in/{profile} returns 404. The profile name in the URL doesn't match a key in config('fanout.profiles'). Check spelling and that the config file isn't cached against an older version (php artisan config:clear).

POST /fanout/in/{profile} returns 401. Signature verification failed. Three things to check:

  1. The secret config value matches what the sender is using.
  2. The signature_header matches the header name the sender actually sets.
  3. For Stripe-style validators, your server clock is within tolerance (default 300s) of the sender.

Deliveries stay in pending and never run. No worker is processing the fanout queue. Run php artisan queue:work --queue=fanout (or add the queue to your existing worker / Horizon supervisor).

encrypted:array cast errors after a key rotation. All historical payloads were encrypted with the old APP_KEY. Either keep the old key as a fallback (Laravel supports APP_PREVIOUS_KEYS), or php artisan fanout:purge if you don't need the history.

A delivery row is stuck in in_flight. This means a worker started the job but crashed before updating to a terminal state. The job will be retried by Laravel's queue framework (or stay stuck if your queue driver doesn't time-out long-running jobs). Manually requeue with Fanout::replay($eventId, endpoint: 'staging').

Stripe complains "no response within 30s" even though I'm returning 202 quickly. Make sure you're not running the Stripe handler synchronously in none persist mode and waiting for downstream HTTP. The receiver always queues delivery — if it's blocking, something else (middleware, app boot) is slow.

My transform callable isn't running. Closures can't be stored in cached config. Either use a class string (recommended), or register the transformer at runtime in a service provider via Fanout::extendTransformer('name', fn () => ...).

Testing

The test suite covers all signature validators, both signers, every branch of the delivery job (success, retry, exhaustion, network errors, filter, transform, signing, throttle, disabled endpoints, terminal short-circuit), persistence in all three modes, replay, encryption at rest, model swappability, and the full receiver-to-destination integration path.

License

MIT — see LICENSE.


All versions of fanout with dependencies

PHP Build Version
Package Version
Requires php Version ^8.3
laravel/framework Version ^12.0 || ^13.0
guzzlehttp/guzzle Version ^7.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package crumbls/fanout contains the following files

Loading the files please wait ...