cego / request-insurance
Package for Laravel that facilitates and ensures that http requests are sent
Requires
- php: ^8.3
- ext-curl: *
- ext-json: *
- ext-mbstring: *
- ext-pcntl: *
- ext-posix: *
- doctrine/dbal: ^3.3|^4.0
- guzzlehttp/guzzle: ^6.5.5|^7.2
- illuminate/console: ^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/database: ^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/encryption: ^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/http: ^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/pagination: ^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/routing: ^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/view: ^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- jfcherng/php-diff: ^6.13
- nesbot/carbon: ^2.17|^3.0
Requires (Dev)
- cego/php-cs-fixer: ^2.0
- open-telemetry/sdk: ^1.0
- orchestra/testbench: ^6.0.0|^7.0.0|^8.0.0|^9.0.0|^10.0.0
- spatie/laravel-prometheus: ^1.2
This package is auto-updated.
Last update: 2026-07-31 08:34:21 UTC
README
Guaranteed delivery of outbound HTTP requests for Laravel.
Instead of firing an HTTP request and hoping it arrives, you insure it: the request is persisted to the database first, then delivered asynchronously by a worker that retries with exponential backoff, keeps a log of every attempt, and hands permanently failing requests to a human through a built-in management UI where they can be inspected, edited (with peer approval), retried, or abandoned.
Use it for requests that must not be lost when the receiving service is down, times out, or your own process dies mid-request — webhooks, downstream service calls, third-party API writes.
Supported versions
| Package version | PHP versions supported | Status |
|---|---|---|
| ^1 | ^7.4, ^8.0 | Security and bug fixes only |
| ^2 | ^8.3 | Security and bug fixes only |
| ^3 | ^8.3 | Security and bug fixes only |
| ^4 | ^8.3 | Active development |
Installation
composer require cego/request-insurance
The service provider is auto-discovered and the migrations load automatically — run them with
php artisan migrate. To tweak configuration, publish the config file:
php artisan vendor:publish --provider="Cego\RequestInsurance\RequestInsuranceServiceProvider"
Creating requests
Build and persist a request with the fluent builder — this only writes a database row, delivery happens asynchronously:
use Cego\RequestInsurance\Models\RequestInsurance; RequestInsurance::getBuilder() ->url('https://api.example.com/webhooks') ->method('post') ->payload(['event' => 'order.shipped', 'order_id' => 123]) ->headers(['Authorization' => 'Bearer ' . $token]) ->traceId($traceId) // optional: correlation id, searchable in the UI ->priority(5) // optional: zero-based, 0 is processed first ->timeoutMs(5000) // optional: per-request timeout ->create();
Processing
A long-lived worker polls for requests that are due and delivers them:
php artisan process:request-insurances
Run one or many — workers coordinate through row locking (SELECT … FOR UPDATE SKIP LOCKED on
MySQL 8+) and claim requests in priority order, batchSize at a time.
Cycle budget
Each batch is sent concurrently in chunks of concurrentHttpChunkSize, which defaults to the whole
batch. A cycle has maximumSecondsPerWorkerCycle (120s) to finish, after which the worker considers
itself stuck and exits — leaving the chunk it was sending to be recovered by
request-insurance:unstuck-processing some minutes later. The worst case is:
ceil(batchSize / concurrentHttpChunkSize) * timeoutInSeconds
The defaults therefore need a single timeoutInSeconds (20s) at worst, well inside the budget, while
lowering the chunk size means checking that arithmetic against it. A chunk size of 1 sends requests
strictly one at a time in priority order, which only fits a small batch or a short timeout.
Concurrency also means requests within a batch complete in whatever order the receivers respond, so
priority orders which requests get claimed, not which arrive first. The same goes for running more
than one worker: a single worker with a chunk size of 1 is the only setup that delivers in strict
order.
The deprecated concurrentHttpEnabled setting is still honoured when set to false, which does the
same thing as a chunk size of 1.
Lifecycle
flowchart LR
WAITING --> READY --> PENDING --> PROCESSING
PROCESSING -->|2xx| COMPLETED
PROCESSING -->|4xx| FAILED
PROCESSING -->|5xx / timeout| WAITING
FAILED -->|retry / edit| READY
FAILED -->|abandon| ABANDONED
Loading
| State | Meaning |
|---|---|
WAITING |
Waiting for its retry_at timestamp before becoming ready |
READY |
Ready for a worker to pick up (default state) |
PENDING |
Reserved by a worker, about to be processed |
PROCESSING |
Actively being sent |
COMPLETED |
Delivered with a successful response |
FAILED |
Received a response or timeout that requires human intervention |
ABANDONED |
Given up on — will never be processed again |
Server errors and timeouts are retried with exponential backoff (retry_factor ^ attempts
seconds, factor 2 by default, capped at retry_cap, 1 hour by default) up to
maximumNumberOfRetries. Client errors (4xx) fail immediately — resending the same request would
just fail again, so a human decides: edit it, retry it, or abandon it. Inconsistent outcomes
(e.g. the process died mid-request) fail by default but can be retried automatically via
retryInconsistentDefault or per request with ->retryInconsistentState().
Maintenance
The package schedules its own housekeeping (offset per application so fleets do not thunder):
| Command | Cadence | Purpose |
|---|---|---|
unlock:request-insurances |
every 5 min | Releases requests stuck in PENDING |
request-insurance:unstuck-processing |
every 10 min | Fails or readies requests stuck in PROCESSING |
clean:request-insurances |
every 10 min | Deletes COMPLETED rows older than cleanUpKeepDays |
Encryption and masking
Sensitive parts of a request can be encrypted at rest and are shown masked in the UI:
RequestInsurance::getBuilder() ->headers(['X-Api-Key' => $secret]) ->encryptHeader('X-Api-Key') ->payload(['card' => $number]) ->encryptPayloadField('card') ->create();
Authorization headers are always encrypted by default — see fieldsToAutoEncrypt in the config.
Events
Hook into processing with standard Laravel event listeners:
| Event | Fired |
|---|---|
RequestBeforeProcess |
Before a request is sent |
RequestSuccessful |
On a 2xx response |
RequestClientError |
On a 4xx response |
RequestServerError |
On a 5xx response |
RequestFailed |
When a request transitions to FAILED |
RequestInconsistent |
When a request ends in an inconsistent state |
All live under Cego\RequestInsurance\Events.
Management UI
The package ships a web UI at /vendor/request-insurances (route name
request-insurances.index) with automatic light/dark mode:
- Pipeline overview with live per-state counts and cursor pagination
- Filtering by trace id, url, date range, and state
- Bulk retry / abandon of selected requests
- Per-request inspection: payload, headers, timings, response, and the full attempt log
- Editing of failed requests (method, url, payload, headers, priority) gated behind four-eyes approval, with a diff view and an audit trail of applied edits
Protect the /vendor path with your application's own auth middleware — the package does not
impose any.
Monitoring
JSON endpoints suitable for dashboards and alerting: /vendor/request-insurances/load (worker
load), /monitor (active/failed totals), and /monitor_segmented (per-state counts). If
spatie/laravel-prometheus is installed, matching
Prometheus gauges are registered automatically.
Development
vendor/bin/phpunit vendor/bin/php-cs-fixer fix