Reader Stacks

Sending a cURL-Style Request With Headers in Laravel

Laravel's Http facade wraps Guzzle under the hood, offering a genuinely more readable fluent syntax than raw cURL or the earlier GuzzleHttp\Client instantiation pattern for the exact same underlying request.

Sending a cURL-Style Request With Headers in Laravel

Making an outgoing HTTP request with custom headers — calling a third-party API from a Laravel backend — is best done through Laravel's built-in Http facade, a genuinely more readable fluent wrapper around Guzzle than raw cURL or manually instantiating a Guzzle client.

A basic GET request with headers

use Illuminate\Support\Facades\Http;

$response = Http::withHeaders([
    'Authorization' => 'Bearer '.$apiToken,
    'Accept' => 'application/json',
])->get('https://api.example.com/orders');

$orders = $response->json();

A POST request with a JSON body and headers

$response = Http::withHeaders([
    'Authorization' => 'Bearer '.$apiToken,
])->post('https://api.example.com/orders', [
    'customer_id' => 123,
    'total' => 150.00,
]);

Passing an array as the second argument to post() automatically sends it as a JSON body with the correct Content-Type: application/json header set — no manual json_encode() call or explicit content-type header needed for this common case.

Checking the response

if ($response->successful()) {
    $data = $response->json();
} elseif ($response->clientError()) {
    // 4xx response
} elseif ($response->serverError()) {
    // 5xx response
}

These convenience methods (successful(), clientError(), serverError()) read the response's status code range — generally clearer at the call site than manually checking $response->status() >= 200 && $response->status() < 300 for the same result.

Setting a request timeout

$response = Http::timeout(10)->get('https://api.example.com/orders');

Without an explicit timeout, a request to an unresponsive external API can hang far longer than acceptable for a typical web request — setting a reasonable timeout gives a bounded, predictable failure case instead of an indefinitely hanging request.

Retrying automatically on failure

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

retry(3, 100) retries up to 3 times with a 100-millisecond delay between attempts — genuinely useful for a flaky third-party API where a transient failure is common and a simple retry often succeeds on the next attempt.

Sending form-encoded data instead of JSON

$response = Http::asForm()->post('https://api.example.com/login', [
    'username' => $username,
    'password' => $password,
]);

asForm() switches the request body to application/x-www-form-urlencoded encoding — necessary for an API endpoint (like an OAuth token endpoint) that specifically expects traditional form-encoded data rather than a JSON body.

Faking HTTP responses in a test, without making a real request

Http::fake([
    'api.example.com/*' => Http::response(['status' => 'ok'], 200),
]);

Http::fake() intercepts any matching outgoing request during a test and returns the specified fake response instead — essential for testing code that calls an external API without genuinely hitting that real API (and its rate limits, cost, or availability) on every test run.

Why Http:: is generally preferred over raw cURL or a manual Guzzle client

Laravel's Http facade wraps Guzzle under the hood but exposes a considerably more readable, fluent syntax — and critically, it integrates with Http::fake() for testing, something raw curl_exec() calls or a manually instantiated Guzzle client don't get without significantly more manual test setup work.