In a previous post I tried to give an introduction on how to get started with PayPal Payment Data Transfers (PDT). PDT is very handy in several cases, but you can’t always rely on it since it requires the user to return to your page after doing the payment. That will often happen, but it’s not guaranteed to happen. If you for example want to mark an order in your system as paid or something like that, you most likely want to use PayPal Instant Payment Notifications (IPN) in addition to PDT.
Instant Payment Notification (IPN) is a message service that notifies you of events related to PayPal transactions. You can use it to automate back-office and administrative functions, such as fulfilling orders, tracking customers, and providing status and other information related to a transaction. — PayPal
Once again the documentation, tutorials and code samples I found on this was a bit all over the place. Sort of messy and outdated. So, once again I decided to do my own thing and just follow the steps required and implement them myself. And since the tutorial on PDT turned out to be a bit of a success, I decided to share this too. Hopefully it can make the lives of fellow developers easier
How it works
The concept is pretty simple. You first give PayPal an address to a listener, for example
http://business.example.com/ipn/listener.php. Then, whenever something happens, PayPal will post information about the event to that address. So for example if someone completes a payment, your listener will be notified shortly after with the transaction details and all you need to know.
This tutorial will focus on the listener part, as the setting up part on PayPal is very simple (Leave a comment if you disagree). Just go to the PayPal account settings, find the IPN settings and give the URL to your IPN listener. For testing we don’t even need to do that. All we need to do is to set up a PayPal Developer Account. We can then use their very handy Instant Payment Notification (IPN) simulator that you find among their Test Tools. So let’s get started!
Step 0: Sign up for a Developer Account
If you haven’t, go sign up for a PayPal Developer account. We don’t need to set up any accounts this time, as we will simply use the mentioned simulator.
Using the simulator is ultra simple. You just enter the address of your listener and choose a transaction type. You can then fill out all the data that should be in this fake transaction and then just hit “Send IPN” at the bottom. Luckily PayPal actually fills out all the fields with random test data, so unless you are trying to test something specific, you can just ignore the data and hit the button right away
Now, to the actual listener file…
Step 1: Catch the IPN
First thing we need to do in our listener is of course to catch the data we get from PayPal. The data is sent as a POST request, so PHP has actually done that for us. We just need to grab it
Step 2: Verification
Since what we got could just be plain bogus from some stranger, we need to verify it with PayPal. This is done by taking all the POST data in its unaltered state, add one field to the beginning, and then send it back. In return we should then get one word,
INVALID. To do that, we can use our old buddy cURL.
if(array_key_exists('test_ipn', $ipn_post_data) && 1 === (int) $ipn_post_data['test_ipn'])
$url = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
$url = 'https://www.paypal.com/cgi-bin/webscr';
// Set up request to PayPal
$request = curl_init();
CURLOPT_URL => $url,
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => http_build_query(array('cmd' => '_notify-validate') + $ipn_post_data),
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_HEADER => FALSE,
CURLOPT_SSL_VERIFYPEER => TRUE,
CURLOPT_CAINFO => 'cacert.pem',
// Execute request and get response and status code
$response = curl_exec($request);
$status = curl_getinfo($request, CURLINFO_HTTP_CODE);
// Close connection
if($status == 200 && $response == 'VERIFIED')
// All good! Proceed...
// Not good. Ignore, or log for investigation...
Note that in the very beginning actually check for a field called
test_ipn. If that exists with a value of 1, it means it’s a request from the sandbox. In other words, we can choose the correct PayPal interface by just looking for that.
You can also see I set two cURL options called
CAINFO. The reason for that you will find in my post on cURL SSL verification.
Step 3: Fix character set
Now that we know the data is valid, we can start to deal with it. What you get could be in a different character set than what you want though, so we should fix that first. The data should contain a key called
charset which specifies what character set the data is using. We just need to check for that and if needed convert from that to what we want, for example UTF-8.
// Ignore if same as our default
if($charset == 'utf-8')
// Otherwise convert all the values
foreach($ipn_data as $key => &$value)
$value = mb_convert_encoding($value, 'utf-8', $charset);
// And store the charset values for future reference
$ipn_data['charset'] = 'utf-8';
$ipn_data['charset_original'] = $charset;
Step 4: Use the data!
Yup, believe it or not, that was everything that was needed to catch an IPN message from PayPal. Where you go from here completely depends on what exactly you need to do.
What you at least should do is the following:
- Confirm that the payment status is Completed.
PayPal sends IPN messages for pending and denied payments as well, so don’t ship stuff or anything until the payment has cleared.
- Use the transaction ID to verify that the transaction has not already been processed.
This prevents you from processing the same transaction twice. You can for example store the transaction id in a database and check against those before you do anything with incoming IPNs. If you’re smart you could also store the time the IPN came in and the raw IPN data. This way you have a log of all incoming messages you can use if you need to reprocess something or for debugging if something weird is going on.
- Make sure the receiver’s email address is the one you expected.
- Make sure the price, item description, et cetera, match what it should be.
And then finally you should of course make sure your customer gets what they should
You can read more about PayPal Instant Payment Notifications in their IPN Guide.
I have put together a working sample you can check out over at samples.geekality.net/ipn. Hopefully this tutorial and that sample can help you get started with all of this
I also put together an IPN handler class you can use pretty much out of the box. The sample uses it, and it’s very simple. Just extend the class, override the
process method and do what you need. Below is a simple example.
class My_Ipn_Handler extends IPN_Handler
public function process(array $post_data)
// Let the IPN_Handler do it's processing,
// which includes validating and fixing the encoding
$data = parent::process($post_data);
// Check if validation failed
if($data === FALSE)
header('HTTP/1.0 400 Bad Request', true, 400);
// Seems it all was good, so in lack of better things to do,
// let's JSON encode it and dump it to a file
file_put_contents('ipn.txt', json_encode($data).PHP_EOL, FILE_APPEND);
$handler = new My_Ipn_Handler();
That’s all! I’m sorry it took so long to get this tutorial written, but hopefully someone can find it useful. I have learned a lot writing it at least! Please leave a comment if it was helpful, if it wasn’t helpful, if there are mistakes, typos, or if you just want to say thank you