Geekery

Since Amazon decided all of their requests needed to be authenticated, developers have been scrambling to convert their existing code to work with their new authentication architecture.

Here’s an excerpt of the email you probably received:

“… signatures will be necessary to authenticate each call to the Product Advertising API. This requirement will be phased in starting May 11, 2009, and by August 15, 2009, all calls to the Product Advertising API must be authenticated or they will not be processed. For pointers on how you can easily authenticate requests to the Product Advertising API, please refer to the developer guide, available here.”

You can find the developer guide documentation they are referring to here:
Product Advertising API

The documentation is very thorough and complete, but doesn’t get to the gist of the problem: what do I need to do in order to make my existing request, still work?

After some research, I came across the work of a couple of individuals, made some slight modifications, and have come up with a function that should help you. Here are the works I am referring to:
Amazon Product Advertising API Signature – PHP
REST Authentication for PHP4

If you’re using PHP and the REST architecture, then you’ve probably got something in your script that creates your URI and fires it off with a file_get_contents and simplexml_load_string.

In this case, you’ve probably already got everything together to create your nice URI. It may look something like this:

$uri = "http://webservices.amazon.com/onca/xml?" .
       "Service=AWSECommerceService" . 
       "&Operation=ItemSearch" . 
       "&MerchantId=All" . 
       "&Condition=All" . 
       "&Availability=Available" . 
       "&Sort={$sort_by}" . 
       "&Version={$amazon_version}" . 
       "&SubscriptionId={$amazon_subscription_id}" . 
       "&AssociateTag={$amazon_associate_tag}" . 
       "&{$amazon_search_type}={$search_title}" . 
       "&SearchIndex={$search_database}" . 
       "&ResponseGroup={$information_requested}";

The new requirements allow you to still use your old code, but there are modifications necessary. A date needs to be added, a signature needs to be created and the keys/URI have to be ordered/formatted correctly with all the necessary character replacements in order for everything to work. It seems like it could be quite a task to write something that works very differently from your previous code.

In the end, I was able to take my original URI and feed it into a function, and let the code continue along it’s merry way. Here’s what I came up with:

/**
  * This function will take an existing Amazon request and change it so that it will be usable 
  * with the new authentication.
  *
  * @param string $secret_key - your Amazon AWS secret key
  * @param string $request - your existing request URI
  * @param string $access_key - your Amazon AWS access key
  * @param string $version - (optional) the version of the service you are using
  */
function getRequest($secret_key, $request, $access_key = false, $version = '2009-03-01') {
    // Get a nice array of elements to work with
    $uri_elements = parse_url($request);
 
    // Grab our request elements
    $request = $uri_elements['query'];
 
    // Throw them into an array
    parse_str($request, $parameters);
 
    // Add the new required paramters
    $parameters['Timestamp'] = gmdate("Y-m-d\TH:i:s\Z");
    $parameters['Version'] = $version;
    if (strlen($access_key) > 0) {
        $parameters['AWSAccessKeyId'] = $access_key;
    }   
 
    // The new authentication requirements need the keys to be sorted
    ksort($parameters);
 
    // Create our new request
    foreach ($parameters as $parameter => $value) {
        // We need to be sure we properly encode the value of our parameter
        $parameter = str_replace("%7E", "~", rawurlencode($parameter));
        $value = str_replace("%7E", "~", rawurlencode($value));
        $request_array[] = $parameter . '=' . $value;
    }   
 
    // Put our & symbol at the beginning of each of our request variables and put it in a string
    $new_request = implode('&', $request_array);
 
    // Create our signature string
    $signature_string = "GET\n{$uri_elements['host']}\n{$uri_elements['path']}\n{$new_request}";
 
    // Create our signature using hash_hmac
    $signature = urlencode(base64_encode(hash_hmac('sha256', $signature_string, $secret_key, true)));
 
    // Return our new request
    return "http://{$uri_elements['host']}{$uri_elements['path']}?{$new_request}&Signature={$signature}";
}

I hope others find this helpful. If you have any comments or changes to the code, feel free to submit them below.

If you liked this post, then please consider subscribing to my feed.

  • Michael;

    Right you are…

    Gave me a scare. I checked our production code and it was, in fact, in the correct order.

    Must’ve fixed it there and not here.

    Thanks much!

  • Michael

    There’s an error in your code —

    $signature = urlencode(base64_encode(hash_hmac(‘sha256’, $secret_key, $signature_string)));

    should read

    $signature = urlencode(base64_encode(hash_hmac(‘sha256’, $signature_string, $secret_key)));

    (the order of arguments was wrong)

  • Nice one. Thanks for sharing!

  • For some reason, I got a signature error when using the code.
    So I changed the line:
    from
    // calculate HMAC with SHA256 and base64-encoding
    $signature = base64_encode(hash_hmac(“sha256”, $string_to_sign, $private_key));
    to
    // calculate HMAC with SHA256 and base64-encoding
    $signature = base64_encode(hash_hmac(“sha256”, $string_to_sign, $private_key, True));

    Then it works!

    Note: the “True” parameter is for output raw binary data.

    Thanks for the code!

  • simon

    I have been looking for code like this. I can run it without getting errors, but how do I get it to print the URL. I need to know how to return an output from the function so I can run a script that parses the xml! thanks.

  • Sam

    Thank’s a lot. That was very useful.

  • Oliver

    Like afterfate I had to set the raw_output parameter of hash_mdac() to TRUE for it to work.

  • Not work for me eighter with or without

    $signature = base64_encode(hash_hmac(”sha256″, $string_to_sign, $private_key));

    $signature = base64_encode(hash_hmac(”sha256″, $string_to_sign, $private_key, True));

    I did it in perl, and I start perl with system()

  • That was immensely helpful. Thanks! I also needed the TRUE for hash_mdac() to get it working.

    afterfate played with the code before he posted his fix. For the record, the fix should be this:

    $signature = urlencode(base64_encode(hash_hmac(‘sha256’, $signature_string, $secret_key, TRUE)));

  • thanks, this helped a lot. for it to work for me, i needed TRUE for hash_mdac() and I also had to change the timestamp to:

    $parameters[‘Timestamp’] = gmdate(“Y-m-d\TH:i:s.000\Z”);

    instead of

    $parameters[‘Timestamp’] = gmdate(“Y-m-d\TH:i:s\Z”);

  • Cheers everyone!

  • Hello Duncan;

    From the looks of things, you should be able to use this function with your code.

    The line where your request in crafted will probably be where you will want to use the function posted here. Before doing:

    $response = file_get_contents($request);

    You should have:

    $newRequest = getRequest($yourSecretAmazonKey, $request, $yourAmazonAccessKey);
    $response = file_get_contents($newRequest);

    I think that should get you what you’re looking for.

  • Duncan Slade

    Hi,
    Just starting out in php/aws programming.

    My crafted (heavily cut and pasted from various sources) request to amazon just started getting refused by amazon.co.uk.
    Your code looks like it might help me, but I don’t really understand how to implement it, feed uri into function??
    My code displays the amazon wishlist of a user with an amazon wishlist id = &listid

    I would love some help in adding the authentication parameters to my request.
    Thank you in advance
    Duncan

    My code at the moment (in a drupal custom views field):

    node_data_field_wishlistid_field_wishlistid_value;
    $request = 'http://ecs.amazonaws.co.uk/onca/xml?Service=AWSECommerceService&Operation=ListLookup&AWSAccessKeyId=AKIAJ6VMM6PWOTPZVCMA&ListId='. $listid .'&ListType=WishList&Sort=Price&ResponseGroup=EditorialReview,Images,ListItems,OfferSummary';
     
    $response = file_get_contents($request);
    if (!$response) {
      die('Web service request failed');
    }
    $xml = new SimpleXMLElement($response);
    ?>
     Lists->List->ListItem as $items) {
      $title = $items->Item->ItemAttributes->Title;
      $price = $items->Item->OfferSummary->LowestNewPrice->FormattedPrice;
      $asin = $items->Item->ASIN;
    $img_src = $items->Item->SmallImage->URL;  
    $desc = strip_tags(
                $items->Item->EditorialReviews->EditorialReview->Content
              );
      if (strlen($desc) > 300) {
        $desc = substr($desc,0,299);
        $desc = preg_replace("/s+[,.!?w-]*?$/",'...',$desc);
      }
      $href = 'http://www.amazon.co.uk/gp/product/'. 
      $asin .'?ie=UTF8&tag=littearn-21&linkCode=as2'.
      '&camp=1789&creative=9325&creativeASIN='. $asin;
      $buy = ' <a href="'. $href .'" title="'. $title .'" rel="nofollow">'.
      'Buy for: '. $price .'</a>';
      $html .= ''. $title .'';
      $html .= ''.
      '<a href="'. $href .'" title="'. $title .'" rel="nofollow"></a>';
      $html .= ''. $desc . $buy .'';
    }
     
    print $html;
     
    ?>
  • Duncan

    Holy crap, that worked!! Brilliant thankyou. I added the $yoursecretamazonket and $your amazonaccesskey variables at the top of my original code, changed my $response line to your suggestion and cut and pasted your function to the top of my entire code. Put that in a block, and it worked, got my wishlists back! thankyou.

    P.S with my newfound enthusiasm I tried to fix another broken part of my site where a user enters his amazon account email address and my site returns his wishlist id(s).

    the my-amzemail comes from a submit form on my Drupal Wishlist Panel page,
    (

    )
    which posts the users email address back to the Wishlist Panel page within Drupal
    the code below takes that email and creates an amazon request which was working before, so I added my 2 new variables, swapped my $response line and pasted the function above the whole code.
    Didn’t work though
    I’ll keep plugging away but any ideas are appreciated.
    Cheers
    Duncan

    &lt;?php
    print &#039;Your Wishlist(s) ' ;
    $listemail=$_POST['my-amzemail'];
    $request = 'http://ecs.amazonaws.co.uk/onca/xml?Service=AWSECommerceService&amp;AWSAccessKeyId=AKIAJ6VMM6PWOTPZVCMA&amp;Operation=ListSearch&amp;ListType=WishList&amp;Email='. $listemail .'';
     
    $response = file_get_contents($request);
    if (!$response) {
      die('Web service request failed');
    }
    $xml = new SimpleXMLElement($response);
    ?&gt;
     Lists;
    foreach($lists-&gt;List as $list){
    $ListURL = $list-&gt;ListURL;
    $ListName = $list-&gt;ListName;
    $TotalItems = $list-&gt;TotalItems;
    $ListId = $list-&gt;ListId;
    print("$ListName ID no.($ListId) has $TotalItems items.");
    }
    ?&gt;
  • Duncan

    I took out the chevrons

    form action=”Wishlist” method=”post”
    input type=”text” id=”my-amzemail” name=”my-amzemail” /
    input type=”submit”
    /form

  • Brian

    I’m using your code and the response coming back from Amazon says that I have the wrong format for the Timestamp. It says:
    Correct format is (ISO 8601 UTC) “YYYY_MM_DDThh:mm:ssZ
    I’ve tried:
    Y-m-d\TH:i:s.000\Z
    Y-m-d\TH:i:s\Z
    Y_m_d\TH:i:s.000\Z
    Y_m_d\TH:i:s\Z

    The encoded param looks right: 2009_08_19T17%3A01%3A35Z

    And a bunch of other combinations and it doesn’t seem to work. I’m trying to do a ListSearch for wishlists on the ecs.amazonaws.com US server.
    Thanks,
    Brian

  • Brian

    Ah, turns out I was passing the urlencoded parameters to my rest client (Zend) and it was encoding them again and corrupting the request.

    I inserted a line at the end of the code that adds a decoded signature to the sorted unencoded parameter array:

    $parameters[‘Signature’] = urldecode($signature);

    And then I return those parameters to my REST client to send.
    Thanks for the great code!

  • Phil

    Thanks works like a charm

  • Duncan

    OK, got it, stupid typo, had my ‘ and ” all over the wrong place in the $request.
    Works fine now, thanks again
    Duncan

  • Keith

    Thanks for the great script.

    Got it working on my laptop in under 5 minutes.

    Only problem is when I uploaded the code to my Linux server it does not authenticate.

    I have been unable to spot any platform specific functions in the PHP script.

    The only differences from inspecting the variables is that the $signature variable comes out 8 characters longer on the Windows machine than the Linux machine.

    Any thoughts?

    Anyone else had the same issue?

  • Worked like a charm, thank you so much!

  • coolmoov

    You Rock!. I have been trying to get my code to work and here it is. You’ve done it! and it works.

    Thanks a Bunch!

  • Well, your code produces no errors for me, but I can’t get it to print the result of the request. Where should I put a print_r() so that I can see what I get? thanks.

  • pHasIs

    I have some problems with $newrequest

    i try this
    $newrequest = getRequest($secret_key, $request, $access_key = false, $version = ‘2009-03-01’);
    then i get a new request with secretkey & timestamp but i still Error when i open a new request on browser

    “is not a valid value for Signature. Please change this value and retry your request.”

    someone help please(i’m noob)

    thank you.

  • Jack Mason

    Thanks for the great script! You saved me weeks of trial and error getting this task accomplished. Great job!

  • HELP! I have implemented every change that have been suggested and I still get the “SignatureDoesNotMatch”. Is it possible for someone to dump a complete script on the site that works?

    Thanks

  • Freddy03h

    You save my life !

  • zyc

    good

  • Pingback: Whoila Blog » Blog Archive » Amazon Affiliate API + PHP()

  • i love you bonnie! Your code works instantly.

  • Thanks so much for your code! we are using it over at LoudSongs.com to get album artwork from amazon. It works great.

  • Pingback: Using the Amazon Product API with AS3 – #FreeCodeSunday | AdenForshaw.com()

  • Steve

    Thanks very much for this code.

  • Eugene

    The script stopping working now? Have anyone test it again.
    I used it to test lookup items.

  • Thanks a lot worked like a charm ;)

  • Mike

    Thank you!!
    I’ve been going through the documentation on Amazon all night, but all examples I’ve been able to find are half-baked at best and don’t even include signing the bloody (REST-)request = examples don’t work.

    With your code though, I had it working within two minutes.
    So thanks again for saving me X hours of digging through the Amazon docs

    Best Regards,
    Mike

  • Ilan

    Thanks dude!
    Good job
    You saved me a few hours (after trying Amazon’s crap examples)

  • ankit

    Thanks for providing detailed method.
    I have used this, but got below error :
    SignatureDoesNotMatch The request signature we calculated does not match the signature you provided. Check your key and signing method.
    I have tried each possible ways by changing the timestamp and using also htt_request2 method., but got the same above error. Please suggest the mistake or solution for the same. Thanks in advance. Please provide response ASAP.

  • ankit

    Thanks for providing detailed method.
    I have used this, but got below error :
    SignatureDoesNotMatch
    The request signature we calculated does not match the signature you
    provided. Check your key and signing method.

    I have tried each
    possible ways by changing the timestamp and using also http_request2
    method., but got the same above error. Please suggest the mistake or
    solution for the same. Thanks in advance. Please provide response ASAP.

  • Pingback: jhanbackjr on "[Plugin: ScrapeAZon] Usable outside of wordpress?" | Wordpress Problems & Erros - How to fix()