PHP Tutorial: PayPal Instant Payment Notification (IPN)

Got a letterIn 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 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. So let’s get started!

Step 0: Sign up for a Developer Account

To access the IPN simulator, you need to login at Seems to be connected with the regular PayPal accounts now, so just try login with your actual PayPal credentials. If you don’t have a PayPal account, sign up for one 🙂

Using the simulator is super simple, just enter the absolute URL to 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 implement the actual listener…

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 🙂

$ipn_post_data = $_POST;

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, VERIFIED or INVALID. To do that, we can use our old buddy cURL.

// Choose url
if(array_key_exists('test_ipn', $ipn_post_data) && 1 === (int) $ipn_post_data['test_ipn'])
    $url = '';
    $url = '';

// Set up request to PayPal
$request = curl_init();
curl_setopt_array($request, array
    CURLOPT_URL => $url,
    CURLOPT_POSTFIELDS => http_build_query(array('cmd' => '_notify-validate') + $ipn_post_data),

// 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.

Note: If the call to doesn’t work at all, curl might have problems with the SSL verification. This happened to me on one of my hosts, so documented it here.

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.

if(array_key_exists('charset', $ipn_data) && ($charset = $ipn_data['charset']))
    // 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:

  1. 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.
  2. 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.
  3. Make sure the receiver’s email address is the one you expected.
  4. 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 paid for 🙂

You can read more about PayPal Instant Payment Notifications in their IPN Guide.

Working sample

I have put together a working sample you can check out over at Hopefully this tutorial and that sample can help you get started with all of this 🙂

The sample uses an IPN handler class I put together, that should be usable pretty much out of the box. Just extend the class, override the process method and do what you need. Below is the simple sample which just appends the incoming messages to a file.

<?php // example listener.php

require 'ipn_handler.class.php';

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! Hopefully someone find this useful. 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, etc. 🙂