Retrying a flaky upstream is a loop, a sleep, an attempt counter and a decision about which failures are retryable — four things that get written slightly differently every time.
$response = Http::retry(3, 100)
->timeout(3)
->connectTimeout(1)
->get('https://pricing.internal/quotes/FR-100');
// retry(times, sleepMilliseconds) — and the callback form,
// which is the one that matters:
Http::retry(3, 100, fn($e) => $e instanceof ConnectionException);
The default retries on any request exception including a 500, which is usually right and is wrong for a 422 — retrying a validation failure three times is three identical failures and a slower error. The callback form is what makes the policy explicit. The separate connect timeout matters as much as the read timeout: an unreachable host should fail in a second rather than at the full read timeout, and the two are frequently set as if they were one.