22

Using the PayPal IPN, I keep getting an error 400.

I have been making the script send me emails of $res to see what the response is, inside of the while (!feof($fp)) {} loop. I always end up getting the error: HTTP/1.0 400 Bad Request

In total I get back:

HTTP/1.0 400 Bad Request
​Connection: close
Server: BigIP
Content-Length: 19
​Invalid Host Header

​The last line after this is just blank. Here is my code, I have tried changing loads of things but nothing works.

$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$value = preg_replace('/(.*[^%^0^D])(%0A)(.*)/i','${1}%0D%0A${3}', $value);// IPN fix
$req .= "&$key=$value";
}

// post back to PayPal system to validate
$header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";

$fp = fsockopen('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);

if (!$fp) {
// HTTP ERROR
} else {
   fputs($fp, $header . $req);
   while (!feof($fp)) {
       $res = fgets ($fp, 1024);
       if (strcmp ($res, "VERIFIED") == 0) {
           //ADD TO DB
       } else if (strcmp ($res, "INVALID") == 0) {
           // PAYMENT INVALID & INVESTIGATE MANUALY!
           // E-mail admin or alert user
       }
   }
   fclose ($fp);
}

I have added a line, this is the header before it is sent:

 Host: www.sandbox.paypal.com
 POST /cgi-bin/webscr HTTP/1.0
 Content-Type: application/x-www-form-urlencoded
 Content-Length: 1096
user1576375
  • 223
  • 1
  • 2
  • 6

6 Answers6

45

Since you're opening the socket yourself, rather than using an HTTP library such as curl, you need to set the proper HTTP Protocol version and add the HTTP Host header yourself just below the POST line.

$header = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$header .= "Host: www.sandbox.paypal.com\r\n";
Community
  • 1
  • 1
Michael Hampton
  • 9,737
  • 4
  • 55
  • 96
  • 3
    You should also change 'HTTP/1.0' to 'HTTP/1.1'. – Robert Aug 08 '12 at 16:46
  • Actually, it is. The 'Host' header is not part of the HTTP/1.0 protocol, so the correct answer is; you'll need to add the 'Host' header *and* move to HTTP/1.1. – Robert Aug 09 '12 at 10:24
  • 1
    I don't understand why this Host header is needed. Two years ago I set up an IPN listner - today when I needed to update it and test it on localhost I found that I was getting errors when POSTing back to HTTP (it now gave 302). After switching to SSL, like in this example I also got 400. And adding Host header worked - but my live IPN already used SSL without the Host header. :s So why does localhost IPS require it? – thomthom Aug 14 '12 at 23:29
  • Also, when I tried HTTP/1.1 the PayPal IPN Tester tool would return an error - it didn't seem to like it. Switching back to 1.0 and everything worked again. – thomthom Aug 14 '12 at 23:30
  • Reading this post at PayPal's forum: https://www.paypal-community.com/t5/Selling-on-your-website/IPN-response-problem/td-p/519862?profile.language=en-gb It's not just me seeing this. Live code works without Host header, but Sandbox bombs with 400. I noticed the PayPal example has been updated from the PHP4 version I used to PHP5.2 with cURL. I guess I'll try and migrate to avoid any potential future problems. – thomthom Aug 14 '12 at 23:41
  • My best guess would be that the sandbox is on a machine serving multiple virtual hosts, while live is only serving www.paypal.com. – Michael Hampton Jun 03 '13 at 04:25
  • 1
    My fix: change from HTTP/1.0 to HTTP/1.1 – sNICkerssss Jan 25 '16 at 13:24
  • @NeedleNoseNeddy The question is outdated, too. I don't understand what you intend by your comment. – Michael Hampton Aug 28 '16 at 19:43
29

I was having the same issues and these are the required changes. Some of the answers above dont fix all the problems.

New format for header:

$header = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Host: www.sandbox.paypal.com\r\n";  // www.paypal.com for a live site
$header .= "Content-Length: " . strlen($req) . "\r\n";
$header .= "Connection: close\r\n\r\n";

Note the extra set of \r\n on the last line only. Also, the string compare no longer works because a newline is being inserted in the response from the server so change this:

if (strcmp ($res, "VERIFIED") == 0) 

to this:

if (stripos($res, "VERIFIED") !== false)  // do the same for the check for INVALID
janelle
  • 291
  • 2
  • 2
  • 5
    This is the correct answer as of Oct-2013, as I tried the other answers and did not work. My PayPal-IPN code is based on this site -> https://developer.paypal.com/webapps/developer/docs/classic/ipn/gs_IPN/ and adding this code above make it works in sandbox ! – Britc Oct 17 '13 at 16:43
  • U just saved me! – Parixit Nov 10 '17 at 08:45
2

https://www.x.com/content/bulletin-ipn-and-pdt-scripts-and-http-1-1

// post back to PayPal system to validate
$header .="POST /cgi-bin/webscr HTTP/1.1\r\n";
$header .="Content-Type: application/x-www-form-urlencoded\r\n";
$header .="Host: www.paypal.com\r\n";
$header .="Connection: close\r\n";
Yasen
  • 3,400
  • 1
  • 27
  • 22
1

I have found PayPals example code using fsockopen not to work properly.

To make IPN work with PHP I used Aireff's suggestion from Aug 5 and looked at the code using curl technique on x.com site.

Thomas
  • 11
  • 1
0

I had the same problem and the best thing is to use the paypal sample code... It works perfectly then : https://www.x.com/developers/PayPal/documentation-tools/code-sample/216623

Aireff
  • 1
  • That sample code has change since two years ago when I first set up my IPN. The old one was made for PHP4 I think - it now fails in sandbox mode by still works live. Though that makes me nervous and I'll be migrating the code to the newer example. – thomthom Aug 14 '12 at 23:42
0

Another solution is to trim the $res before comparison..

$res = fgets ($fp, 1024);

$res = trim($res); //NEW & IMPORTANT
zhangyangyu
  • 8,520
  • 2
  • 33
  • 43