- How do I install caseyamcl/guzzle_retry_middleware in a Laravel app?
- Run composer require caseyamcl/guzzle_retry_middleware. If your app is still on Guzzle 7, which is the default for most Laravel 8+ installs, require version ^2 instead because the latest release targets Guzzle 8. There is no service provider or config publishing step; the package is a single middleware class that you push onto a Guzzle handler stack.
- Which package version supports Guzzle 7 versus Guzzle 8?
- Version 3.x requires Guzzle 8, while version 2.x supports Guzzle 6 and 7. Stock Laravel 9 through 12 ship Guzzle 7, so composer require caseyamcl/guzzle_retry_middleware:^2 is usually the right constraint. If you have explicitly upgraded to Guzzle 8, use the latest default release.
- Does this middleware work with Laravel's Http facade?
- There is no first-class Laravel bridge, so you attach the middleware to a Guzzle HandlerStack and give that stack to a client. You can try Http::withOptions(['handler' => $stack]), but that path is undocumented and should be verified on your exact Laravel and Guzzle versions. The safer approach is a singleton Guzzle client or an Http::macro that reuses one configured client.
- What is the difference between this and Laravel's Http::retry()?
- Laravel's Http::retry only re-fires when an exception is thrown, so a 429 or 503 response is treated as a normal response unless you explicitly call throw(). It also ignores the Retry-After header entirely. This middleware inspects the status code, honors Retry-After delta-seconds and HTTP-date values, and applies configurable backoff.
- How do I prevent POST requests from being retried and causing duplicate charges?
- Set retry_on_methods explicitly to idempotent verbs such as GET, HEAD, PUT, DELETE, and OPTIONS, or to a per-integration allowlist. The default is '*', which means POST requests are retried and could duplicate charges or orders. Treat this as a required production control before enabling the middleware for any payment or order API.
- How does the middleware handle Retry-After headers and backoff?
- If the response includes a Retry-After header, the middleware waits for the server's instructed period, supporting both delta-seconds and HTTP-date formats. If the header is absent, it falls back to its configurable backoff schedule. You can override attempts and wait behavior per request, since request options take precedence over client options and middleware defaults.
- Can I customize which HTTP status codes trigger a retry?
- Yes. retry_on_status defaults to 429 and 503 but accepts any list of status codes. You can set it in the factory options or override it per request through Guzzle request options. That per-request merging is useful for letting individual integrations or tenants tighten or loosen retry rules without rebuilding clients.
- How do I log or observe retries in production?
- Use the on_retry_callback option to run code each time a retry fires, which is where you can log the URL, attempt number, status code, and wait time. That gives you visibility into retry storms without modifying the middleware itself.
- What happens if I combine this middleware with Http::retry() or queue job retries?
- Retries multiply. An Http::retry(3) plus a middleware configured for 10 attempts can mean up to 30 outbound calls per request, and queue job $tries stack on top of that. Assign retry ownership to exactly one layer and lower max attempts inside queue workers to avoid hammering an API that is already returning 429.
- Is caseyamcl/guzzle_retry_middleware production-ready and actively maintained?
- The library is well engineered: PSR-12 compliant, PHPStan level 8, near-100% test coverage, and a maintained changelog. The main risk is governance, since it has roughly 222 stars and a single primary maintainer, so pin a version and review upgrades. Its retry defaults are aggressive for most apps, so tune max_retry_attempts, retry_on_methods, and backoff before production.