Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

stevegoddard14's avatar

Http vs Guzzle

I'm attempting to integrate PayPal checkout info a site (actually an upgrade from older Express Chekout). Anyhow, as part of the integration, you have to make a number of API calls to PayPal:

  • Generate Access Token
  • Create Order (send details of what the user is buying)
  • Capture Payment (check if the payment has been processed)

The first 2 of these are working fine. But i cannot get the final one to work. Here's where it gets weird. After trying for ages using Laravel's Http wrapper and failing, I contacted PayPal support. They didn't really add to what I already knew from the JSON error: "The request JSON is not well formed". However they did note that "Your system is passing an extra character in the request body so you will need to fix this from your end. " But there is no body to this call. Only headers and the PayPal ID in the URI.

After much head scratching I decided to trying using Guzzle directly - which worked!!

So my question is: what is the difference between these 2 code blocks. The Http one fails, the Guzzle one works. I'm baffled. But there clearly is some difference.

Using Laravel Http (does not work)

$access_token = $this->generateAccessToken();
$this->url = $this->base_url . '/v2/checkout/orders/' . $paypal_id . '/capture';

$this->headers = [
  'Content-Type' => 'application/json',
  'Authorization' => 'Bearer ' . $access_token,
];

//Does NOT work. "system is passing an extra character in the request body". Result in an invalid JSON message.
$response = Http::withHeaders($this->headers)->post($this->url);
$json = $response->json();
return dd($json);

And using Guzzle (which works)

use GuzzleHttp\Client as GuzzleClient;

//This works.
$client = new GuzzleClient(['headers' => $this->headers]);
$response = $client->request('POST', $this->url);
$json = $response->getBody(); //Actually a PHP stream...
return dd(json_decode($json, true));

Thanks, Steve

0 likes
3 replies
opinedals's avatar

Hey Steve,

I'd like to offer a potential solution, though I regret to inform, even after nine months, the issue persists. I find myself in the same predicament: Laravel Http client remains non-functional, while Guzzle, utilizing identical parameters, performs as expected. Small difference, this pertains to basic authentication.

I will persist and test, but for the time being, it appears that Guzzle is the solution.

$headers = [
        'Content-Type' => 'application/json',
        'Authorization'=> 'Basic '.base64_encode('email:token')
];

Laravel Http Client (doesn't work). Also tried the "withBasicAuth" and "acceptJson" options, but the result is the same.

$response = Http::withHeaders($headers)->get("url");
return response()->json($response);
200 Response: {"cookies":{},"transferStats":{}}

Guzzle Client (works)

$client = new GuzzleHttp\Client(['headers' => $headers]);
$response = $client->get("url");
return response(json_decode($response->getBody(), true));
200 Response with expected body

Thanks, Oscar

Jsanwo64's avatar

i am guessing the error giving the difference is

$response = Http::withHeaders($this->headers)->post($this->url);

which should be

$response = Http::withHeaders($this->headers)->post($this->url, []);
2 likes
Jsanwo64's avatar

The code does not define a body for the POST request. Sometimes, the post method may return an empty body or an unexpected extra character, resulting in an invalid JSON message. This can happen if the server expects a specific payload or if there is a tiny difference in how the request is formed. GuzzleHttp requires explicit processing of the request body.

By default, Guzzle may process the request body in a manner that is more consistent with the server's expectations. This is probably why the Guzzle version works but the Laravel HTTP client version does not.

2 likes

Please or to participate in this conversation.