PHP Tutorial: PayPal Payment Data Transfers (PDT)

MoneySay you have a PayPal “Buy Now”-button on your website and you have assigned return URLs like and You can then welcome the user back after a successful payment. But what if you wanted to say something more interesting than just “hey, welcome back” when they click on that “Return to Merchant”-button? And can you know if the order was actually done or canceled? Maybe you’d like to log the transaction in your database and mark a payment as complete or something like that too? In that case you sure can’t trust a simple flag in the address bar…

Payment Data Transfer (PDT) is a secure method to retrieve the details about a PayPal transaction so that you can display them to your customer. It is used in combination with Website Payments Standard, so that after a customer returns to your website after paying on the PayPal site, they can instantly view a confirmation message with the details of the transaction. — PayPal

I’ve tried to figure out how to use PDT and found that most samples and classes to build from are usually quite ugly, old or outdated. I didn’t find them too useful anyways… So, therefore, I’ve tried to do my own thing based on the documentation found on the PayPal Developer websites. (Seriously, how many versions of documents and developer websites do they have anyways? It’s like a complete jungle…)

Since the documentation was a bit of a mess, I thought I make a small tutorial on the steps needed to get started. That way I can learn it better myself and hopefully help some other poor souls that need to figure this stuff out as well. Please provide feedback if you have any! Would love to make this page nicer and clearer if possible πŸ™‚

Step 0: Sign up for a Developer Account

Before you start playing around with PayPal stuff, I would recommend that you get a PayPal developer account. You should also set up a business account in the PayPal Sandbox which you can then use instead of your real account. A lot safer, cheaper and easier than using a real one…

Step 1: Enable PDT for your PayPal account

For this step I’ll just quote their PDT page (one of many, it seems):

Apparently they have changed their UI now, so the instructions in their PDT documentation is wrong… Thanks for the heads up Yuriy! The description below should be correct now πŸ™‚

To use PDT, you must activate PDT and Auto Return in your PayPal account profile. You must also acquire a PDT identity token, which is used in all PDT communication you send to PayPal.

Follow these steps to configure your account for PDT:

  1. Log in to your PayPal account.
  2. Click the Profile subtab.
  3. Click the My Selling Tools button in the left column.
  4. Find the Selling online section and click Update in the Website preferences row.
  5. Under Auto Return for Website Payments, click the On radio button.
  6. For the Return URL, enter the URL on your site that will receive the transaction ID posted by PayPal after a customer payment.
  7. Under Payment Data Transfer, click the On radio button.
  8. Click Save.
  9. Find the Selling online section and click Update in the Website preferences row again.
  10. Scroll down to the Payment Data Transfer section of the page and take note of your PDT identity token.

The last point is particularly important and you should not only view the token, but note it down. Reason being that you need this in your code when we’re going to talk to PayPal later.

Screenshot of PayPal Payment PreferencesAs an aside, I would also recommend you turn off the “Block Non-encrypted Website Payment”, at least while testing, since it’s a lot easier to make PayPal buttons on the fly without encrypting them. I currently don’t know how, but I’ll probably try to figure out soon. I would also recommend that you set PayPal Account Optional, so that people can make payments without signing up (since some people don’t want to be bothered with that). To the right I have put a screen shot of that page with the mentioned options marked in yellow. Click on it for full-size view πŸ™‚

Step 2: Create a button

The PayPal buttons consist of a form which has a number of hidden key/value pairs and a submit button. You can find info on how to make those in their Website Payments Standard Integration Guide so I won’t explain how to do that here. What I would like to mention in this case though, is that you can, if you need to, override the return URL that you set in the previous step for specific buttons. For example you can return them to the order they paid for rather than just a general return page.

<input type="hidden" name="return" value="" />
<input type="hidden" name="cancel_return" value="" />

I use the same URL for both since we will check things with PDT anyways. Here is an example button with the data I use in this tutorial:

<form action="" method="post" accept-charset="utf-8">
    <input type="hidden" name="cmd" value="_xclick" />
    <input type="hidden" name="charset" value="utf-8" />
    <input type="hidden" name="business" value="" />
    <input type="hidden" name="item_name" value="Stuffed bear" />
    <input type="hidden" name="item_number" value="BEAR05" />
    <input type="hidden" name="amount" value="20.00" />
    <input type="hidden" name="currency_code" value="USD" />
    <input type="hidden" name="return" value="" />
    <input type="hidden" name="cancel_return" value="" />
    <input type="hidden" name="bn" value="Business_BuyNow_WPS_SE" />
    <input type="image" src="" name="submit" alt="Buy Now" />

So, the user will click the button, go through the payment procedure, and at the end there will be a link/button to return to the merchant. When that is clicked, we get to the next step.

Step 3: Catch the return

The URL they are returned to could for example look like this (split for easier reading):

As you can see we get some GET parameters to work with here, but the only one you need is the Transaction ID, tx:

  $tx = $_GET['tx'];
  // Further processing

The reason we don’t care about the rest is that they’re not trustworthy at all. Since they are GET parameters we can easily tamper with them in the address bar of our browser, and we just can’t have any of that, can we?

To get good and trustworthy data we need to send this transaction id back to PayPal together with our PDT Identity Token. If the id and token are valid, we will get real and actual transaction details back in return.

Step 4: Do a POST request back to PayPal

Sending the Transaction ID and our PDT Identity Token back to PayPal can be done in various ways from PHP. I used cURL which I found to be pretty clean and easy to use.

// Init cURL
$request = curl_init();

// Set request options
curl_setopt_array($request, array
  CURLOPT_URL => '',
  CURLOPT_POSTFIELDS => http_build_query(array
      'cmd' => '_notify-synch',
      'tx' => $tx,
      'at' => $your_pdt_identity_token,
  // 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

The cURL options we set means roughly: Use this URL, do a POST, send these variables, return the transfer as a string when I call curl_exec and do not include the HTTP headers in that string. The reason behind the last two are in this post. I’ve commented them out, and if it works without them, just leave them out. For me it worked fine on one server, while on another I had to add them.

Note: For security reasons, PayPal will only allow you to use the transaction id to get a response up to 5 times. After that it will be invalidated and you will get an error back instead of the transaction data. Fair enough for regular use, but found it a bit annoying while developing since it took some tries to get the result I wanted πŸ˜›

Step 5: Check the response

The response will be a string that looks something like this:


To make sure everything is well we must check that the status code is 200 and that the first line says SUCCESS. On failure it would say FAIL, or something like that. We can do that like this:

if($status == 200 AND strpos($response, 'SUCCESS') === 0)
    // Further processing
    // Log the error, ignore it, whatever

That response is not very useful right now though. Even if it is valid, the string is just, well, a long string. Not to mention it’s URL encoded and possibly in the wrong character encoding. We want to clean it up and turn it into a much more handy associative array.

Step 6: Clean up the response

We want to remove the first line, URL decode the string, turn it into an associative array and convert the encoding if needed. This is how you might do that:

// Remove SUCCESS part (7 characters long)
$response = substr($response, 7);

// URL decode
$response = urldecode($response);

// Turn into associative array
preg_match_all('/^([^=\s]++)=(.*+)/m', $response, $m, PREG_PATTERN_ORDER);
$response = array_combine($m[1], $m[2]);

// Fix character encoding if different from UTF-8 (in my case)
if(isset($response['charset']) AND strtoupper($response['charset']) !== 'UTF-8')
  foreach($response as $key => &$value)
    $value = mb_convert_encoding($value, 'UTF-8', $response['charset']);
  $response['charset_original'] = $response['charset'];
  $response['charset'] = 'UTF-8';

// Sort on keys for readability (handy when debugging)

The $response should now contain a nice and sorted associative array with the correct encoding and it’s ready to be used for whatever purpose you need.

Step 7: What now?

What you do with the validated and cleaned up data depends on what you need and what you want. You could use it to display a nice helpful message to the user, store it somewhere, maybe mark something as paid in your database, et cetera. You should also most likely check that the data you have gotten corresponds to what you expected. For example if the amount they paid matches the amount they should have paid and so on.

Important thing to remember about PDT

You are actually not guaranteed to get this PDT data. Unless the user clicks that last button in the payment process at PayPal and returns to your page, you will get nothing. They could for example close the browser tab when the payment is done or the power could go out just before they had the opportunity to. Theoretically it could happen!

If you want something that is fully reliable, you need to have a look at PayPal Instant Payment Notifications (IPN). PayPal sends these notifications to a certain URL you have provided behind the scenes and it is not part what the user does or does not do. They may not be instantaneous, but you are guaranteed to eventually get them.

What you most likely want is a combination of these two. Use PDT to give the returning users a quick response and store what you can/need in the database. Then use IPN to fill out the gaps of missed PDTs or missing transaction details (IPN will give you many more details than PDT does).

It took some time, but I have finally finished a tutorial on IPN Check it out if you’re interested 8)

Working sample

Since the code above was a bit broken up, I have gathered it all in a single example page that should be operational. It contains a simple Buy Now button and it will display the PDT response when you return from the fake purchase. Since it uses the PayPal Sandbox you need to be logged in to your developer account. For the payment you can use one of your existing test accounts or you can pretend to be a person that just wants to use his credit card without an account (If you need help coming up with a fake credit card number that validates, have a look at

You find it over at

The process_pdt function you can see in the source code should be pretty self-contained and you should be able to just drop it in to whatever you are working on and use it out of the box. Just remember to fill in your actual identity token πŸ˜‰

And that’s the end of this tutorial!

Think this was my first longish in-depth post, so if you have any feedback, found it helpful, please leave a comment. Also do that if something is unclear, could be improved or is flat out wrong. Especially if it’s flat out wrong. There’s a lot of wrong stuff on the Internet, and I don’t want to contribute to that πŸ˜›

  • Thanks for the awesome post! I’m trying to integrate PayPal with a reservation system plugin I’m building for a client and couldn’t agree more that the PayPal documentation is all over the map – very hard to determine what I need to do. Your tutorial helped clarify things a lot – it’s great!

    I set up Website Payments Standard and it works well for me – even pre-populating address information based on the WordPress user registration data (a person has to be registered to make an online reservation). Of course the problem came when I needed to confirm payment before I could confirm a reservation (reservations are a custom post type and I’m using the a “draft” post status to indicating a pending reservation – updated to “future” post status when the reservation is confirmed – not possible to create a reservation in the past “published” post status). It’s really too bad that a person using PayPal has to choose to return to the calling site instead of being re-directed there after payment is complete. It looks like a combination of PDT and IPN is the way to go – too bad it’s so complicated.

    It gets even trickier for me since a pending reservation can expire if payment is not received in a given time period (20 minutes is suggested by my client). This means that a person could make a payment using PayPal for a reservation that has expired and they will need to have their money refunded. I suppose I can check if the expired reservation dates are still available and allow them to confirm the reservation anyhow – tricky…

    Anyhow thanks again as this post basically tells me exactly what I have to do on the PayPal side of things – although I have to say I’m sort of dreading the implementation.

    Another thought – maybe there is an existing WordPress plugin that can be integrated with, or at the very least whose code can be used as a base – like this one?

    • Glad you found it useful! Kind of funny I must say; from what you say it seems like you are trying to make something very similar to what I have been working on, hehe. Yes, these things get complicated, so I’m happy that the PayPal stuff can make it a bit all easier.

      Yes, PDT + IPN is the way to go. And how they work are quite similar. The verification is a bit difference, but once you have the data it is pretty similar how you use it and work with it.

      I will try to write a tutorial on how to do IPN sometime. I already have working code for it, and it’s not that complex. The complex parts are of course how you use it in your own project.

      On the matter of refunding, if you play around in the PayPal sandbox, you will discover that Refunds are actually pretty easy to do. And I think it can be done from your code as well through one of their APIs. Might research that later myself and perhaps write some about it when/if I figure it out πŸ™‚

  • Curran

    Thanks very much. I was having difficulty with PayPal myself. Great tutorial. Would love to see your IPN post! Cheers!

    • Thanks! I’d love to see it too. I have it pretty much mapped out in my head and I know how to do it. Just have to find time to write it… Perhaps tomorrow evening actually. Stay tuned! πŸ™‚

  • I am trying to build confidence before adding payments to my cart process and this is just the sort of thing I need.

    Still need to work out encryption but this takes me a long way forward. Thanks.

  • Aslam Shah

    Thank you lots !

  • Sneha

    Hi, I am java developer.
    Thanks alot. You have put all important stuff together in very simple and easy word.
    I want to use PDT+IPN for my site, but not clear how I ll achieve it. Your post will surely help me.

    • Hi! Good to hear it was useful. I really want to get the tutorial on IPN written. Annoys me that I haven’t gotten time to do it yet. Hopefully soon, hehe.

  • Great tutorial. Took me some time though to figure out that when not using SSL I had to change the URL to CURLOPT_URL => ‘’. Works now and I’m very happy with this. The script that paypal provides seems a bit outdated although it works. What I still dont get about Paypal is that you do the check after the payent has been made. Wouldn’t it be great to generate an encrypted key from the information in your form and your PDT token. This could then be checked on the paypal website before a buyer makes it’s payment.
    Anyways, thank you very much for sharing your thoughts about handling PDT!

  • I just read my previous comment. What I meant is that I would like to do a check before making a payment so the buyer knows he’s transfering his money to the right account. Of course there should always be a check after the payment is made as well.

    • You can actually encrypt the form data if you want. I didn’t do that here to keep it simple, but for example if you generate the PayPal button through the PayPal button factory (or what it’s called) you can choose to have the button encrypted. That way the form data can’t be changed. This can also be done on the fly in your code.

  • alex maier

    Hey, I am currently a computer science student still in college. I just made a website recently where I have a java applet for a dice game that I made. I can show it on the website no problem, but I want to be able to only show the applet after someone has paid through paypal.

    I took your exact code and changed only 2 things. I changed $your_pdt_identity_token, to be '8gCOx0S4FW-g6s0Pd8kDPl95UXR2ZG5m_Qt2hh6mqhwyK3i-nse6S4aiqMm', (which is my token id for the sandbox account) and changed “” to “” (which is my email address for the sand box).

    After uploading it to my web-server. It looks exactly the same as yours, when I click PayPal button it sends me to my test store and can go through the whole payment process, but when it auto returns me to my site, it doesn’t show me the thank you message. It does show me this,

    GET: Array
        [tx] => 6KL19869KN546713P
        [st] => Completed
        [amt] => 20.00
        [cc] => USD
        [cm] =>
        [item_number] => BEAR05


    Where there is a few things in the GET but nothing in PDT. As well no thank you message. Do you know why this is happening and what to do to fix it?

    Also, when this is fixed, do you know where I could put the code ” ” (which shows the applet) so that it will show after someone has paid.

    Thanks a lot in advance for the response!

    • You are probably getting an error message back from PayPal or something which contains the answer for that. Check the response you get and for any cURL errors in the function. For example:

      echo curl_error($request);
      echo $response;
    • where to put the code depends on what and how you’re doing it. Your function returns false, so you can just have a check somewhere for that.

      However, remember that you can only use the transaction id 5 times. Which means if the user reloads the page 5 times, they can no longer access your thing, even though they have paid. And that would certainly annoy me if I was a user of that. So you should probably rather store the result in a database with some sort of login system or something so that the user can return to your page and use your applet any time.

      • alex maier

        I just don’t know php well at all. Is there someone or some company I can pay a little to write this php code for me?

        I am fine with the user only being able to refresh the page 5 times. I want to charge just over about $1.00 to use the scoreboard and they won’t need to refresh any more than that. If you refresh the page then you would loose all your data from the scoreboard.

        • Most likely yes. I’m not one of them as I have my plate full already, but sure you can find a coder to code for you. That’s usually how programmers survive. Writing code for other people πŸ˜‰

  • sarmen

    i noticed in the source there is a hidden field called amount that holds the amount value of what is being sold. is it possible to do this another way? because anyone can come and change that value to buy something for free.

    • This is also why we have PDT and IPN. Because when someone pays for something, you shouldn’t just ship anything to them because they paid. You should ship it when you have confirmed with PayPal (through PDT or IPN) that they in fact have paid the correct amount.

      If you want to be really secure, however, you can encrypt the form contents.

  • I have integrated IPN successfully, and know I’m trying to add PDT. I have enabled it in my paypal, also enabled auto return and set up the location, as well as enabled IPN. However I can seem to get the tx variable when paypal return to the return page I do get a GET string but I’t looks alot like the IPN string I believe they are identical, in that string I find tdn_id variable witch I have come to find is the same value as tx. do you have any idea what I am doing wrong and why I dont just get the tx variable fron sandbox.paypal?

    • I tested the sample code I made, and it still works. If you use PDT and have set it up correctly you should be getting the following GET variables: tx, st, amt, cc, cm and item_number.

      It could maybe be related to what they talk about in this thread:

      With regards to the issue with PDT not returning the ‘tx’ variable in the return URL when using PDT, that issue should have been resolved through a recent system update. I also tested it from here and it works.

      Otherwise, make sure you have used the correct token, correct URLs, et cetera.

      • Hey thanks for the response, I got it working not with CURL for some reason I couldn’t get it to work with curl I should check if it’s installed I guess. but I got it to work with fsockopen() I have removed the headers successfully and validated for Success and status code 200. I am trying to remove “SUCCESS” with substr() but can’t do it like you do it because I get “3a0” before “SUCCESS” any idea why?

        • I didn’t use strpos() to remove success but when I turned it into an associative array fixed char encoding I was removed automatically. you think this will be trouble in the future?

          • No idea! There are many different ways you can do what I did. The one I chose was simply the one I understood best and the one which was fastest in my benchmarking.

            If you have tested your way to do it well, it should work πŸ™‚

  • I use your code to check the success of transactions but the $_POST and $payment_data are both completely empty…

    Been trying to do this for 5 days now, using my code, paypals code, third party code, your code…

    • Do you get the transaction id through the $_GET variable?

  • Manish Pal


    I used your code for payment data transfer. The PDT sample in your website – is showing perfect values.

    And after the payment it is automatically having few extra parameters in its url –

    But when I am using your php source, there are no such extra parameters. And also none of the values are showing up, i.e. first_name, mc_gross, mc_currency.

    Can u help in this ???

    • Not sure what you mean now. You mean the transaction id and such when returning from the paypal payment? Those do not appear? Sure you have enabled PDT in your PayPal settings?

      • Manish Pal

        Yes, I have enabled PDT.
        I think the transaction id is not returning.
        Do not know why, but it is happening ?
        Hence need your help….

        Just for example from my first question, I have given two links for your website itself.
        The first link is of the original webpage in which you have given PDF sample,
        and the second link is the page after the payment is made.

        The second link is having – transaction id, status, amt, cc & item number – in its url.

        I have enabled PDT in my account, bu then also when I am using the php source code, it is not showing any transaction id, status, etc – is not showing up.

        Hope, now it is clear…..

        • Checked all the variables in the script? Do you have the correct business name? Are the return and cancel urls correct?

          • Manish Pal


            Above is the url for testing the PDT with your php source code.
            Have changed only the business email and token.

            I think there is a little difference in the php source and the sample webpage you have given, because the tx, st, cc, amt, is not coming in the url.
            Something is missing.

            Hope by seeing you will know it better.

            Manish Pal

  • @Manish Pal:

    Tried it out, and you’re right, I can’t see any parameters coming in either. Are you sure you have enabled PDT correctly with the business account you’re using?

    • Manish Pal

      I am trying some other scripts for PDT.
      Hope I will succeed.

      Till than, I personally thank you very much for all your support and time you have given.
      Thanks from my heart.

      Manish Pal

      • You’re welcome! Please let me know if you figure out what was wrong πŸ™‚

        • Manish

          It took me so many days, my problem is solved.
          I have used your script, but it didn’t worked.

          Then after I used the sample php script given by PayPal.
          PayPal’s sample script is working great……..

          Thought to show you the below link –

          In the last confirmation page – I showed all the required data which i want to know about the payment and the payer.

          You are a professional and your advise is required – if i am missing something!

          As a simple layman – it makes me very happy to complete one small project.

          Thanks and Regards,
          Manish Pal

  • Mamunul Mazid

    You are great boss. For few days i am searching for step by step process to integrate paypal. I didnt find. This tutorial is very easy to understand and I am going to implement it today.Thanks, For this tutorial.

  • hi. great tutorial. when will be the IPN part come? (:

  • Glen

    I can’t thank you enough. I have coded a sophisticated wallet-based payment system before in PHP using Intellipay, including automated recurring txns and lots of bells and whistles. I practically built a banking application. It wasn’t easy and is a complete overkill for what I need today on a new project. So I thought PayPal would be a breeze after that.

    OMG! Their website and documentation is so bad, and ways of doing things insecure and filled with holes. Everybody who implements PayPal has to go through this?? It’s hard to believe. I spent the last few days hunting for answers in the documentation which you describe as a jungle. That’s a polite way of putting it. I then looked at alternatives like Amazon checkout and Google checkout instead, but they aren’t exact replacements for what you can simply do with PayPal.

    Then I found your website last night. Wonderful – thank you, thank you! I went to bed happy! You fill in the gaps, pointing out the important stuff and warning us of the pitfalls. It’s so simple once you know. Thank you so much for making the IPN tutorial too.

    Why can’t PayPal employ a few people like you?

    • I know right? Really wish PayPal and a bunch of other enterprise companies would stop being so enterprisey. Sooo much marketing talk and jargon talk and blah. Incredibly annoying when you’re a developer trying to figure stuff out and make something πŸ™

      Happy I can help someone!

  • Ivan

    Thank you very very much for your info, you are awesome.
    I would also like to point out that there is a little mistake in your regex (after urldecode($response)); it should be something like this:

    preg_match_all('/([^=\s]++)=([^\s]+)/m', $response, $m, PREG_PATTERN_ORDER);
    • Thanks for the feedback! The added ^ might make it more correct, although it does work as it is, as far as I know. Your [^\s] would make it break if there was whitespace in a value though.

      • Ivan

        You are right; fixed:

      • Ivan

        Mmm… i was doing some tests without the linke breaks so the special character dot in the regex wasn’t breaking the selection after each value; my mystake.

        • Actually, what I already have should be the correct one. Forgot I already had the leading ^ there: /^([^=\s]++)=(.*+)/m

          It goes: Beginning of a line, anything that’s not a whitespace or equal sign, equal sign, anything that’s not a new line.

          The ++ and *+ are possessive quantifiers, which speeds up the matching.

  • Yuriy

    Activate PDT.
    They (PayPal) probably changed the “profile” page layout (I haven’t seen previous). There is no column “Seller Preferences ” anymore.

    But there is a “My selling tools” Menu Item on the left instead. And when you click – on the right – there is Group “Selling online” with item “Website preferences” inside. When you click “Update” – then you see “Auto return” and “PDT” settings – actually this is a page exactly as screen shot in your tutorial.

    Thanks for your tutorial!

    • Thanks for the heads up! Checked it out, and you were indeed correct. Updated the tutorial so it should be correct now πŸ™‚

  • Hey man, step 6 code is ugly, slow (because of PCRE) and complex enough, one could use this instead:

    // make query string from that string and parse it
    parse_str(str_replace("\n", '&', $response), $params);

    // one line, better?
    echo '<pre>';
    • Hey, thanks for the feedback! The way I did step 6 was the fastest I could come up with at the time. Even tested various ways of doing it, and that was the fastest and cleanest way. Not sure I tried anything similar to yours though… and it does look quite simple and efficient, I must say.

      Have you tested that it works with the rest of the code and with what you get from PayPal?

  • Hey Torleif,

    For the past couple of days (yes, days!) I’ve been in and out of the PayPal website, finding myself with tens of browser tabs open on the PayPal domain, struggling to find a decent example of how the PDT/IPN combo should work and your post here along with it’s IPN counterpart is by far the clearest and most effective post I’ve seen about integrating PayPal WPS into a PHP site.

    Thank you!

    • Thanks for the positive feedback! Glad to hear it helped you πŸ™‚

  • Sandro

    Really great tutorial, thanks very much!
    But I have problems with umlauts (such as: Àâü). When Paypal (SandBox) returns item_name the umlauts are no longer legible.
    Have you any idea how I could solve the problem?

    I set by sending:

    and the request:
    and by requment curl_init
    CURLOPT_ENCODING => ‘utf-8’,

    • Well, obviously an encoding issue. But can’t help you other than telling you to check the encoding πŸ˜› Make sure you’re sending what you think you’re sending, et cetera.

  • ronald


    Is it possible to download the whole script. Still new to php and trying learn and understand the whole process.

    thanks . ..

    • If you look at the sample you’ll find the whole source there.

  • Michael Smith

    Great tutorial, Torleif! It’s made things a lot clearer for me (and I’m working through the IPN one now).

    The problem I’m having today is that I got PDT working fine in the sandbox, but going to the live PayPal site, I don’t get any GET values back from my AutoReturn (no st or tx, for example). As far as i can tell, I’ve got both the sandbox and live accounts set up identically (Auto Return on, PDT on, different id tokens noted).

    GRRR!!! Anyway, thanks for your great work!!


  • can’t get your PDT code to quite work. the sample works well. i tried to extract code from index.php and input it into my html (and renaming file .php) but after the paypal transaction is complete. When i return to the webpage after paypal completes the transaction(i’m using the sandbox), my webpage is blank. I can see the transaction data in the URL, but my page doesn’t load and i don’t display the transaction data. do you have an email where I can contact you? i code appears to be “right there” just need the nudge to complete. new to php & xml. thx

    • Do you have a web server set up to properly process php files? Try to create a PHP file with only the following contents:

      <?php phpinfo();
      • a few things: yes, i created the page and got info…version(4.3.11), configuration, etc…

        i am using enabled logging of my scripts and found i was getting a Parsing error on line 56 …
        “foreach($response as $key => &$value) ”
        should be
        “foreach($response as $key => $value)”

        now what gets displayed is the data in my token file..which is at the top of my .php file. is there a way to have it not display?
        also, i know i have a few things i need to straighten out and it is detailed. write to my email and I can forward u the info…thx again.

        • FYI got another error :
          “PHP Fatal error: Call to undefined function: curl_setopt_array() in /process_pdt.function.php on line 23”
          fixed by updating to PHP version 5.3.6 and all is well for now.

  • i get no payment data. all i get after the
    GET: and the transaction data is “PDT ” and empty space follows. Do I not get payment data because i didnt create the form data as you did in index.php? how could I get that info from paypal?

    • reggie

      More info :

      i get
      CURL_INIT Resource id #3
      from the curl_init()

      but i get nothing from these two
      $response = curl_exec($request);
      $status = curl_getinfo($request, CURLINFO_HTTP_CODE);

      do i need to set a timer or delay?

      thx again

      • reggie

        problem solved. for testing i don’t want certificates
        so set ” CURLOPT_SSL_VERIFYPEER => FALSE, ”

        this got me a response.

        thx again

  • Don S.

    On the PDT Sample page, the “View Source” do not display anything. It does have the following error shown at the bottom of the Sample page:

    Webpage error details

    User Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; GTB7.2; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 1.1.4322; InfoPath.3; BRI/2; BO1IE8_v1;ENUS)
    Timestamp: Mon, 16 Jan 2012 20:25:16 UTC

    Message: Expected identifier, string or number
    Line: 17
    Char: 3
    Code: 0

    I like your tutorial; very clear. However I would like to have the combined source code.
    Thank you…….

    • Hm, that’s strange. I can view the source of both files in the PDT sample without any problems. What browser are you using?

      • Don S.

        Thanks for the reply. I was not looking in the right place. However, you website does exhibit the error message which I noted in my previous post. I am using IE8.

        Thanks again….

  • Toroid

    in Sandbox there is no My Selling Tools button it is the PDT stuff
    is under Website Payments Prefefences .
    Excellent tutorial, your comment about using PDT and IPN most useful.
    Nowhere in the mess that is the PayPal manuals does it give any idea about this. Finding information was an absolute nightmare, so big thanks.

    • Oh really? Maybe it’s a bit different from the regular site? Maybe they’re not updating them in sync or something…

      Yeah, it was a huge pain when I tried to figure this out as well, which is why I decided to note it all down in case I or others needed it again πŸ™‚

  • Martin

    Hi Torleif,
    Just thinking… is there any way of specifying a return URL that includes the tx variable e.g. download.php?tx=TRANSACTIONID without using auto return and PDT?
    I’ve tried and tried but don’t think it’s possible. I only ask because I don’t really have a need for PDT – all my background work is done with IPN.
    I’d just like to send my visitors to a download page (they’re purchasing a digital download) without having to worry about a “thank you for your payment” message, which I’d prefer to display on PayPal.
    The download URL is emailed out (via the IPN page) as well as displayed as a link from PayPal, and I don’t want to display the thank you message if the user’s come from the email link.
    Thanks again for all your help with the two tutorials!

    • Not exactly sure what you’re trying to accomplish, but could you perhaps just use the download.php file as the PDT return uri? or have a page with “Thank you for the payment, here’s your download” and that download link?

  • jev

    This really is a great resource.I’d even struggled befor finding this with getting my sandbox account working!
    The main problem I had was understanding Certificates – still don’t! I kept getting cURL errors about the path of cacert.pem. Anyway I did what somebody else had suggested and set CURLOPT_SSL_VERIFYPEER => FALSE,

    All is now fine. Many thanks for taking the trouble to write this.

    • Yeah, you either need to set it to FALSE or specify the path. When specifying the path you of course also need to make sure to put a certificate file at that path πŸ™‚

  • Utsav

    Well I know I am over a year late on this article, but yet, I tried using Paypal PDT and all i get is st=pending never do I get Completed which is why the CURL returns FAIL.

    What probably could be wrong? I am stuck here for quite some time now and any help would be much appreciated.

    • Utsav

      Got It! It was with the currency issue. My account had USD as deafult and It seems I hadn’t allowed direct approval of other currency (AUD) to be precise. It’s done now.

      • Well there you go πŸ˜‰ Good to hear you figured it out!

  • nickyc

    Great tutorial

    In case it helps anyone else here’s the problem i had, and (what seems to be) a solution.

    I was posting to and getting the expected reply (tx etc)
    I was then creating a curl post back to that address, but paypal wasn’t talking back to me.
    I used the curl_getinfo($ch, CURLINFO_HTTP_CODE) from your article and found a 302 HTTP error.
    I changed the url in my curl post from http to https and all is now well, so far..

    Thanks again!!

    • Yup, needs to be https I think. Good to hear you got it working πŸ™‚

  • Sergey

    I wonder why you do

    preg_match_all(‘/^([^=\s]++)=(.*+)/m’, $response, $m, PREG_PATTERN_ORDER);
    $response = array_combine($m[1], $m[2]);

    Why not?

    $response = explode(“\n”, $response);

    Just curiosity.

    • Because that was the fastest solution I found for splitting the whole response into key-value pairs πŸ™‚

  • Stuart MacCaulay

    I am lazy trying to fiddle with code as many other people are but who won’t admit it. Get us a plugin for this, even if premium.

    • And what would this plugin of yours do exactly?

  • Pawan Kumar

    When return from paypal i got this error
    “”Parse error: syntax error, unexpected T_STRING, expecting ‘)’ in wwwrootsubscribesuccess.php on line 19”

    and line 19 is
    17. ‘cmd’ => ‘_notify-synch’,
    18. ‘tx’ => $tx,
    19. ‘at’ => $uG0B76u2miOQ-0llP8uPAh8VncwC0nRhtE1N3iuv2wavMkMbsgvdl29vwxm,

    • um, what in the world is $uG0B76u2miOQ-0llP8uPAh8VncwC0nRhtE1N3iuv2wavMkMbsgvdl29vwxm? Is that a variable with a crazy name? Or should it really have been a string?

      • Pawan

        this is my Identity Token

        • Yeah, if that’s really your code, I don’t think that’s valid PHP syntax.

    • Yeah

      Change to:

      ‘at’ => ‘$uG0B76u2miOQ-0llP8uPAh8VncwC0nRhtE1N3iuv2wavMkMbsgvdl29vwxm’,

  • Ken Slater

    This was really quiet helpful. I tried the published PayPal flow, and I just could not get it to work, and found debugging difficult and slow. Decided to give your code a go, and it just worked. Thanks so much, you have made my life much easier (a volunteer webmaster for a non-profit).

    Now I have to code to handle the multitude of possible error conditions, but this task seems much less painful now that the SUCCESS flow is operational.

    • Good to hear! I actually wrote this while working as a volunteer for a non-profit too πŸ˜‰

      Getting the success-flow working is a great first step!

  • Tim

    I’ve been trying for a month now to get the details back form paypal and this is the first that has helped. Stupid paypal documentation. I’m still new to PHP and am learning as i go. But now i have a problem when the data comes back.

    I have copied exactly as you have and changed the identity token to my sandbox account. Made a copy of the cacert.pem and put it in the same directory.

    But i came up with nothing when printing the final array. Tracking back through, i found i am getting Resource id #6 at the $request variable at the end of step 4.

    I tried putting FALSE at CURLOPT_SSL_VERIFYPEER as ‘reggie’ did (dated October 25, 2011) but this didn’t solve the issue. And this happens for any transaction i have with the paypal sandbox. I’m stuck as to what i can do.

    • You should check the status and if there are any curl errors with the curl_error function.

      • Tim

        I tried the curl_error but it didn’t show anything. Just came up blank when i used curl_error($request)
        Not sure if that is how it is supposed to be referenced.

        However, it did bring my attention to a value that was not set. $tx

        i put in $tx=$_GET[‘tx’]; after $request = curl_init(); and it worked immediately.

        Not sure if this helps identify the original problem but it is solved now.

        Great piece of code this. Simple and easy to follow. Excellent.

        • Tim

          Yeah. looking back on your code i see where i missed the line. i missed it when bringing it across.

          • Yeah, missing the transaction id will absolutely make it not work πŸ˜‰

  • laszlo

    Hi Torleif. I’m a bit puzzled about the part: “Follow these steps to configure your account for PDT”
    Which account ? In my case there are 4:
    – real PayPal account
    – real PayPal Sandbox account
    – fake seller
    – fake buyer
    To me, the account that should be configured for PDT is the fake seller.
    I can indeed follow the configuration steps using my real PayPal account.
    But my real account shouldn’t have anything to do with the sandbox testing.
    To make matters worse, both my real accounts use the same credentials.

    • laszlo

      After some digging I stumbled upon an information that states:
      “To activate PDT you must have a PayPal premier or business account rather than just a personal account.”
      But this defeats the purpose of sandbox testing. I don’t want to create a premier or business account just yet.

      • “Your account” refers to the seller/business account. For sandbox testing you need to set up a fake seller/business account. For production use, you need to set up an actual seller/business account. Simple as that πŸ™‚

        • laszlo

          That’s true, but my point is that the sandbox account is useless unless you have a real premier or business account.
          Thanks for your reply and the articles. They really save people a lot of time and frustration.

  • deniele

    your tutorial is great, i had tried your code, all goes smoothly, perfect ….,
    once again, thank so much….

  • Hi, thanks for the awesome tutorial. I’m having a bit of issue getting it setup. I’m confused in your process_pdt.function.php script; the first list include ‘token’; Where/what is token? I am getting an error when I tried to use this that no file or directory exist.

    Also, just to verify, the includes for the process_pdt.function.php script goes in the returned file after the paypal purchase, correct?

    Thanks in advance for any help you can offer.

  • che

    hi.. please help me with my paypal problem..
    i understand that i need the returned transaction id from paypal to work with PDT, but my problem is when the “If you are not redirected within 10 seconds, click here.” is clicked, it doesn’t return the transaction id or tx in the url, unlike when it is auto-redirected it returns the tx in the url.. can i remove that link or do something for it to return the tx in the url? thanks..

  • Joey Caparas

    Hi there,

    Thanks for this brilliant piece of code. I actually got to familiarize myself with the CURL library and REGEX while learning PDT.

    For those who are receiving null values, you can change:

    ( $status == 200 && strpos($response,'SUCCESS') )


    ( $status == 200 && strpos($response,'SUCCESS') === 0 )
    • Good to hear! Was familiarizing myself with CURL when I wrote this too πŸ˜‰

      About your change, isn’t that what I already have in my code?

  • Hi Torleif,

    Thanks for the tutorial, it made light work of the paypal code they produce.

    I had everything working until recently. I now get an error when I return to my site from paypal. The GET array is there but not the PDT.

    I’m given an error that the token hasn’t been included however, the PDT token from my sandbox account is there in the process_pdt function.

    I’ve tried it with my live token as well but had no luck.

    Any ideas?


    • Hi again,

      sorted it! I had to comment out the CURLOPT_CAINFO => ‘cacert.pem’,



  • RandyHeber

    Nice saved me a bunch of time Thanks

  • I agree with Chris. There seems to be an issue right now with the certificate of Paypal. I have commented out
    CURLOPT_CAINFO => ‘cacert.pem’ to make the page work. It still works either way.

    • Tried to clear it up in the post now. Those last two curl options are only really necessary if it doesn’t work without them. For one server it worked fine for me without, while on a different one I had to add them.

  • Anybody know of a way to specify a unique identifier for a payment that will be passed back to you from PayPal via PDT/IPN? Considered using item_code but that seems a bit hax?

    Otherwise how do you know what payment a PayPal transaction_id relates to?

    • I don’t remember the name of the field, too long since I’ve been working with PayPal stuff, but I remember there was a field you could set to a unique identifier. When that is set PayPal will also prevent a duplicate id from being processed.

    • Minztine Propaganja

      I know that this is an old post and I am too late for this but I just want to help for others. I use this to send
      <input type="hidden" name="custom" value="”>

      the transacID of course is unique.

  • I am not sure why but when I tried this I got no response and status is 0. Any idea what could be the issue here?

    • Try commenting out these lines, if you haven’t already:
      CURLOPT_CAINFO => ‘cacert.pem’,

      • I figured that out.. thank you!

        • Lucas

          I am also having this problem.
          Doesn’t matter if I comment those lines, there is no response and status returns 0.

          Could you elaborate what you did to fix this, please?

  • Rafael

    This is a great article buddy, got me from no idea how to use it to fully implement it on my site in 30 mins, thanks a lot you should add a donate button so I can buy you a beer when my client pays me

    • Good to hear πŸ™‚ I don’t drink, so you just spend those money one something healthy πŸ˜‰

  • Zoltan

    Hi, I cannot get a working code πŸ™ . I have created a paytest.php with these codes and below them a button form. When I press “add to cart” button i can see the item, I can login with test acc to paypal, i get back acc details but when I press “pay now” it just waits with a rotating loading icon. If I turn off payment data transfer then it goes back to paytest.php (without pdt data of course). Could you please advise me what should I check?

  • Purvi

    Gr8 tutorial.. But when I am trying it, return url does not send any parameters. not even transaction id “tx”
    It does come back to my site, after clicking on return. But wout any details

    • Sure you’ve activated PDT in the PayPal settings?

    • Jason

      I am having the same issue, not receiving the tx parameter back. Weird because I have everything set up correctly. I have a standard account do I need a paid account?

  • Vineet Garg

    Good One..really helpfull πŸ™‚

  • LikeMyStyle

    Hi, Im sorry sir…but i just followed ur tutor on my live server, it seem’s it shows only GET result, and i cant get PDT result…perhaps the $payment_data[‘xxx’] was not working tho?

  • TharinduDG

    Is there a way that I can send some data to the customer via the IPN ? It says that the values under the ‘custom’ variable are never presented to the customer.
    My intention is to pass some sensitive information to the customer after he/she had made a payment.
    Please Help.
    Thank You πŸ™‚

    • If you want to display something sensitive, you should probably display it to them on your website or in an email. After the IPN has gone through. So I’d probably either email them the info when you get the confirmation through IPN, or email them a link to a page with the info.

  • Thanks for such a clear & thoughtful guidance. Kudos!

  • Alihadith Tua

    hi. iwant use “ipn” not “pdt”, what are the post variables for the shipping details of the buyer when using ipn?

  • Alihadith Tua

    thanks, i have solved the issue on my website, this s great tutorial, πŸ™‚

  • Terry Bullock

    I’ve been looking at PayPal’s confusing and self-conflicting documentation all damn day, and yours is the clearest, easiest explanation I’ve seen. Wish I’d come across this page earlier and saved myself some coffee… Great tutorial!

  • yeah

    Does anyone know how to make this work with subscriptions? I mean, it does work, but I can’t seem to get data like the amount for each payment, trial period, etc…

    • your dad


  • PrzedsiΔ™biorca

    Thanks this is very helpfull

  • dksgjhdfjk

    Thank you

  • Deepak

    Thnnxxxxx, you have solved my problem .

    You did really a god work .


  • Armand

    Thanks for great tutorial, very helpfull

  • Pingback: Como implementar el pago por Paypal en un sitio web » Blog OpenAlfa()

  • Pingback: How to implement payments with Paypal on a web site » OpenAlfa Blog()

  • Topher Hunt

    Thanks Torleif – life saver post!

  • Martin

    I would like to ask: The paypal account “web preference” Return URL setting must be same to form post variable “return”?

    • The web preference is a default return URL which you can then override using the form post variable. So no, they don’t have to be the same.

  • Sam

    Thank you for writing this tutorial. I was so confused with the Paypal documentation jungle. I finally understood the difference between PDT and IPN


    Big thanks for this tutorial on Paypal PDT in PHP – I got all I needed from and more. Seriously, thanks πŸ™‚

  • Akshat

    Great πŸ™‚

  • michael

    Thanks, this is a great help!

  • lllll

    Thank you, this lessno is great for me. (bow)

  • Rob

    Very helpful post..thank you

  • dave

    just to let you know that you should also check out the paypal code here

  • Brieneke

    Torleif, Thank you for your clear article πŸ™‚ finally managed to get it to work! I had some trouble getting payment data (parameters) back to my success/cancel view. For others who have the same problem:

    For me it turned out to be the hidden input field ‘return’ and ‘cancel_return’. They returned without the parameters and where overwriting the ‘Retour-URL’ set in paypal that had the parameters. So leave them out of your form. Hopefully it works for you too! πŸ™‚

    • If I understand you correctly, your return URL shouldn’t have *any* parameters in them, neither in your form or in paypal. There are data-fields for that.

  • Vik

    Great tutorial. Helped alot and saved so much of my time.
    Thanks !!

  • Pingback: The care, feeding, and breeding of PayPal on your web site | API()

  • Vedran BrnjetiΔ‡

    Oh thank you man so much, Very helpful.

    • Vedran BrnjetiΔ‡

      You might just clear up which URL to use once we are happy with testing and want to go live.
      ” CURLOPT_URL => ‘’ ”
      I believe you just ommit the sandbox part, but it should be explicitly written somewhere.

  • Gary

    Can we still use the hidden $custom, I am having terrible trouble getting it to work on my main paypal but worked ok in the test version

    • You tell me. Haven’t worked with this for several years now. Seems to still work for a lot of people, but try see if they have updated their APIs or something.

      • Gary

        It used to work for me on my old server… Thanks
        but I had a crsf protection that stoped it on the new I find out today in the codeigniter part, but its still not getting through now I have disabled it. So I must have changed something or they have that stops it working.

  • Evan@Zentech IT

    Thanks Torleif, this provided some useful guidance. I discovered that if you already have recent (< ~1 month) transactions in the target paypal account, you can use the transaction IDs to test the PDT POST request/response. The responses for some parameters will change due to different transaction type, but all the fields are present except for the custom button field if you are using one. Hope this helps.

  • David D

    Great script! But, at the top of the process_pdt function the “include ‘token’;” does not include anything and causes a error at the top of the return page. Is this a placeholder for something or am I missing, or need to create, a file? I did put my identity token after AT =>”” and everything else seems to work fine. Thanks.

    • I just moved the the actual token into a constant defined in a separate file. Just remove the include and replace TOKEN with your actual one. Or define the TOKEN constant in an included file, which can be good if your code is in source control, which it should be.

    • ‘token’ is just a PHP file that defines the TOKEN constant used on the AT => line. Was just to not have the actual token in the visible source. As long as AT gets your actual token, you can remove the include line.

  • nick

    i get
    Fatal error: Class ‘string’ not found in /paypal/lib/PayPal/Common/PPModel.php on line 164
    so can you suggest the problem where i missing something

  • maya

    Thank uu very much..This is very helpful page.I am new in this paypal integration industry and I would like to know what is containing in the token,config files in your sample. I could not see that 2 files in your sample..Pls reply asap…

    • It just contains a single constant with the paypal token required for the request.

      • Maya

        Is that contains client ID & client Secret only??And config file contains DB connection code only..right??

        • Maya

          Thank you very much for your response.

          • Maya

            I have one more question.Do you know how to add 2 extra fields(discount,Service fees)in paypal order summary page.I tried different codes.But no result.I am doing a project PHP ,MySQL.In this project,customer don’t want a cart…If you give a sample to add some extra fields,then that would be great…Thank uu

          • Check the PDT documentation. There should be a way to add some custom fields that you can get back in the later PDT response so you can display it. So long since I worked on this, so I don’t remember how, but it should be in the documentation.

          • Maya

            Is that token a file??I cannot understand what contained in that file..Can you please share that code also???

          • Maya

            GET: Array
            [tx] => 87W87475LB0113531
            [st] => Completed
            [amt] => 20.00
            [cc] => USD
            [cm] =>
            [item_number] => BEAR05


            How did you get these above information in Example??I also need to get that information.Can you please help me to solve this problem ASAP??This is the last pblm..

          • All you need is already in the sample. If you’re unable to read what happens from the PHP code, you should learn more about programming and PHP before doing a project like this which involves money transfers. There’s only two simpe files, index.php and process_pdt.function.php, both available in the sample.

          • Dude, calm down πŸ™‚ Like i said, the file only contains a single constant with the PayPal token used in the code. If you don’t know what a constant is, you should really learn more about PHP and programming before attempting to build a solution involving money transfers.

        • No, it’s only the paypal token. The config referred to in index.php is just contains some stuff to make the page layout and such work. Not related to the PDT sample. There is no DB used by the samples.

  • Juno

    If I wanted to store the customer’s first name, last name, email, and the item name in my database how would I go about doing that?

    • Make sure the data contains what you think it does, and that the database gets the data like you think it should. Other than that, I can’t help you. What you’re asking is a super simple and generic operation that you should be able to find out by reading simple documentation about PHP and your database. Try if you have problems.

      • Jahslove1

        Your response here is too hash. It could be simple for you but not for everybody out there. This is best tutorial I have ever seen on PDT; since the whole documentation of paypal is one of worst website entities out there. After reading this great article, the first thing that came to my mind is ‘you should have added a simple and short example of application of the PDT data’. For instance, storing it in a database or using it to automate another script action. That would have perfected your great job. In all I want to thank you so much for this. May continue to God bless and fortify you.

        • Probably could’ve worded it differently, but I still stand behind the fact that what’s asked is a generic. quite well documented thing. Sure, I could’ve covered it here, but then the post would’ve gone outside the subject at hand which is PDT. The complete function in the working sample ( ) returns, on success, an associative array with all the data, so storing it is just a matter of dumping it somewhere πŸ™‚

  • scot

    For the life of me could not work out what I was doing wrong!
    Everyone seemed so happy , ……….. until
    // CURLOPT_CAINFO => ‘cacert.pem’,

  • Thanks a bunch! Finally a decent tutorial for pdt!

  • Nate

    Thanks for this great article! I’m heading over to check out your IPN stuff as well. Not sure if you thought about it or not but perhaps a “Donate” button on here as I would gladly donate to you for your hard work on this!

    • Did have a Flattr button for a while, but was hardly used, and frankly, I don’t really need it at the moment πŸ™‚

  • Miguel

    I’m trying to follow this greatb tutorial. But… in step 3. I don’t get any paramenter πŸ™
    What could be the problem?

  • Amy

    This was so useful and explained very well. Thank you! Just what I was looking for.

  • Greg Whitburn

    Thanks for the explanation Torleif! It still took me quite a while to get the PDT response from PayPal, but your page made life much easier than paypal’s documentation.

    For anyone else who is having trouble, these are the steps that helped me get the PDT response working.

    1. In the “process_pdt.function.php” code, I edited the following:
    include ‘token’;
    changed to

    2. I commented out the following:
    CURLOPT_CAINFO => ‘cacert.pem’,
    (as the discussed below, some servers need it commented out, some don’t.)

    3. Remember to update the account owner email address in the button code:

    I know they’re probably obvious for some people, and they can be found if you read through all the comments below, but it took me a lot of time and frustration to get it to work, so hopefully these points can speed it up for someone else. Thanks again Torleif, it was really really helpful. I can’t believe how un-userfriendly paypal is.

    • If you follow the tutorial, you shouldn’t run into any of those issues. If you however just copy the sample I made without understanding what it does and the adjustments you need to do, then yes, it will be problematic. For example, like I wrote when I presented the sample: “Just remember to fill in your actual identity token”, and that of course goes for the form variables like business email and such too.

      Either way, glad it could be of help and good luck with whatever project you’re using it in πŸ™‚

  • R.E.

    Thank you!!
    Trying to integrate PayPal with my website and the tutorial I was following showed that the PDT identity token was provided directly below the ON/OFF toggle buttons…… not in my account it doesn’t – gah!
    Long chat to a chap at PayPal and he said he’s never heard the term PDT Identity Token’ (where do they find these ppl?) and just told me to speak to my website developer (which being a word press site, is me).
    Soooooo happy to find your post advising me to make all the initial steps in the PDT section… then SAVE… then return to the preferences to retrieve the Identity Token. Voila – it was there.
    Much appreciated πŸ™‚

  • Bob

    Can someone modify the text in the hidden inputs before it is sent to Paypal, and change the payment amount?

    • Most certainly. Any HTML and Javascript of yours can be messed with, which is why Step 4 where you validate the payment server side is so important.

  • Ketul Patel

    this is a great, but right now this code not working today… Why…?? any problem..??
    please give me any suggestion…

  • Π€ΡƒΠ» РСс

    Does someone knows why would the $response be bool(false)? I found nothing about this. Also it would be very helpful if someone knows about a good documentation? (can’t find anything useful in the official one)

    • Where? If after curl_exec, check curl_error.

      • Π€ΡƒΠ» РСс

        Oh, such a lame question, thank you for reminding me how stupid I am πŸ™‚
        And I will use the opportunity to ask one more (hopefully not so dumb) question: What may be the reasons to get the “SSL connect error”?

        • Was it what Nate reported? You commented that it was the case in one of your projects… (

          Otherwise, not sure. What I can think of is old curl version, outdated certificate store, invalid “target” certificate (but that shouldn’t be the case with paypal), … don’t really know what else it could be.

          • Π€ΡƒΠ» РСс

            Old curl version was the problem in one of the servers and outdated certificate was the problem in the other one :/ Thank you for both of your replies, thank you to Nate, too πŸ™‚

          • Nate

            Anytime! I was able to update my curl and openssl version to the latest and greatest and that indeed solved the issues I was having. PDT and IPN are both functioning correctly now.

    • Nate

      Just an FYI, paypal updated a bunch of their tls backend security a few weeks back, in both their live and sandboxed sites. It broke both my PDT and IPN implementations because I was running an outdated version of openssl and curl. The error I was receiving the SSL handshake error. I haven’t fixed it yet, but I believe updating those will resolve the issue I was having.

      • Π€ΡƒΠ» РСс

        Indeed, that was the case in one of my projects.

  • Mohini


    You wrote very good article.
    I have a problem that
    I am getting response as follows when I sent “tx” to paypal :

    [transaction_subject] => Centuary

    [payment_date] => 06:18:15 Apr 18, 2016 PDT

    [txn_type] => subscr_payment

    [subscr_id] => I-4TV31L907XXM

    [last_name] => buyer

    [option_selection1] => $5

    [residence_country] => US

    [item_name] => Centuary

    [payment_gross] => 5.00

    [mc_currency] => USD

    [business] =>

    [payment_type] => instant

    [protection_eligibility] => Ineligible

    [payer_status] => verified

    [payer_email] =>

    [txn_id] => 57331000LX9264923

    [receiver_email] =>

    [first_name] => test

    [option_name1] => Monthly Packages Methods

    [payer_id] => CW7376EU2X4WW

    [receiver_id] => JZZY6MMC6Z47Q

    [payment_status] => Completed

    [payment_fee] => 0.45

    [mc_fee] => 0.45

    [mc_gross] => 5.00

    [charset] => windows-1252

    [] =>

    How to recognize which users is this transaction? I am not getting how to process. I want to store this details into database. Please expand a bit.

    • payer_email? Additionally, I’d recommend you look into IPN, as PDT isn’t fully guaranteed to happen (if for example the customer just closes the browser/tab instead of returning to the PDT page.

  • akash varlani

    Helped me too. Thanks πŸ™‚

  • Terry Wysocki

    For some reason, it appears that the PDT doesn’t work if the payment was defined as recurring. Either I’m not getting $tx or the $status and $response are different. Anyone else have problems with this?

  • Donny.Paul.1972

    Thank you for this .. Ive worked with Worldpay and Sagepay in the past, & Paypal is a complete nightmare in comparison, only surpassed by Barclays lol πŸ˜‰ Next stop rocket science course on Udemy πŸ˜‰ haha Cheers

  • Bryan Rayan

    Nice guidelines and really helpful… Thanks alot

  • Peter Attwood

    Might sound obvious, but, PDT doesn’t work with subscriptions which have a free trial period, as no actual transaction takes place until the end of the free trial.

  • Alan

    Hey thanks for this helpful code – appreciate your time! One thing with using PDT is the query string that is returned can be modified by a user who can then manipulate the outcome of a sale. What’s the best way to prevent that? Is there any way to obfuscate or hide the querystring?

    • And that’s exactly why you don’t use anything in the query string (except for the transaction id, tx), but instead uses PDT to get the actual, untouched values, directly from PayPal.

      • Alan

        Yeah, I use IPN to update the DB, but I want to use GET for displaying a status message on the success.php page – e.g. Completed or Pending etc… However, I don’t like the using GET because of the displayed variables. So I tried to use POST on the return url (success.php) – but POST only seems to work on IPN.php. my return URL only works with GET for some strange reason!

        • Sounds like you’re using it correctly then. Just need to shake off the fear of GET parameters πŸ˜› Remember you’re getting reliable values directly from PayPal on the backend, so the user can’t mess with them.

  • arliacne

    Great tutorial. My assumptions: At the head of process_pdt.function.php is: “php include ‘token’;” This is a file without an extension which resides in the root. This holds the token created from the Website Identity (shown in your image above). However, it is not assigned to a variable unless it is assigned inside the “token” file which is included. What am I missing?

    Also, your example is auto returning. Although I have auto return enabled, it doesn’t – and it is starting to give me an inferiority complex. Perhaps it does not auto return because my token is not being assigned to a variable?


    • Yes, there is a file called “token” without an extension. It defines the constant named TOKEN which you can see used in the code.

      If you don’t specify your token somehow, your code will naturally not work, yes…

  • Vijay Gupta

    Thank you so much for this article, I was searching around the web from last 3 days, watched videos on youtube an 80hours free course(in 2 hours, LOL), read 10-15 articles however everything was so confusing, everyone talks about the old method of enabling Auto Return and PDT… and the PAYPAL… Normal Mode, developer mode, sandbox mode… my god…:) However finally I understood the concept from your article…will implement soon in our upcoming project in a day or two… once again., Thanks a lot bro.. u rocks…!!! Really made my day..!!! πŸ™‚ PS: Might be will ask some questions in near future, please be a bit kind!!

  • James

    Thank you, really helpful.

  • Yug

    Hai, I’ve been trying to integrate PDT.But, I’m unable to figure out where I’m going wrong. Please help me.
    Here are the source code links:

    • When asking for help, please don’t just ask for help. Help with what? What’s going wrong? What’s not working? What were you expecting? Have you read through the blog post again? Do you understand what the code you have actually does, or did you just copy a bunch of stuff hoping it would work? Are you getting any error messages? Have you tried printing out stuff to find where things go wrong?