Laravel Magazine
Http::retry(): Resilient Outbound API Calls With Automatic Backoff

Http::retry(): Resilient Outbound API Calls With Automatic Backoff

Eric Van Johnson ·

Every app that talks to a third-party API eventually hits a moment where that API has a bad few seconds: a timeout, a 503, a rate limit response. Laravel's HTTP client has built-in retry support that handles this without you writing a manual loop.

The basic form

use Illuminate\Support\Facades\Http;

$response = Http::retry(3, 100)->get('https://api.phparchitect.com/inventory');

This retries up to 3 times, waiting 100 milliseconds between attempts. By default it retries on connection errors and any response Laravel's client considers a failure once you've called throw() or set up failure handling, but the real power shows up once you customize it.

Exponential backoff

Passing a fixed delay is fine for quick blips, but for anything talking to a flaky external service, exponential backoff avoids hammering a service that's already struggling:

$response = Http::retry(times: 5, sleepMilliseconds: function (int $attempt) {
    return $attempt * 100; // 100ms, 200ms, 300ms, 400ms, 500ms
})->get('https://api.phparchitect.com/inventory');

Passing a closure instead of a flat number lets you shape the delay curve however fits the service you're calling. A common pattern is doubling: 100 * (2 ** ($attempt - 1)).

Only retrying specific failures

You don't always want to retry every failure. A 422 validation error will fail the exact same way on attempt five as it did on attempt one, so retrying it just burns time. Use the when callback to be selective:

$response = Http::retry(3, 200, when: function (\Exception $exception, \Illuminate\Http\Client\PendingRequest $request) {
    return $exception instanceof \Illuminate\Http\Client\ConnectionException
        || ($exception instanceof \Illuminate\Http\Client\RequestException
            && $exception->response->status() === 503);
})->get('https://api.phparchitect.com/inventory');

This retries connection failures and 503s specifically, while letting 4xx client errors fail immediately since retrying them wouldn't change the outcome.

Throwing after retries are exhausted

By default, if every retry attempt fails, Laravel throws the underlying exception. You can opt out of that and inspect the response instead:

$response = Http::retry(3, 100, throw: false)->get('https://api.phparchitect.com/inventory');

if ($response->failed()) {
    Log::warning('Inventory API unavailable after retries.');
}

Combining retry with a timeout

Set a per-request timeout alongside retries so a single hung request doesn't eat your entire retry budget waiting on a connection that will never resolve:

Http::timeout(5)
    ->retry(3, 200)
    ->get('https://api.phparchitect.com/inventory');

Where this earns its keep

Any outbound call to a service you don't control, a payment gateway, a shipping rate API, a webhook you're forwarding, benefits from this pattern. It turns "our checkout page occasionally 500s because the tax API had a hiccup" into "our checkout page silently retried once and the customer never noticed," which is a much better failure mode for the same amount of code.

Stay Updated

Subscribe to our newsletter

Get latest news, tutorials, community articles and podcast episodes delivered to your inbox.

Weekly articles
We send a new issue of the newsletter every week on Friday.
No spam
We'll never share your email address and you can opt out at any time.