This week the marketing department at my company decided that they wanted to make a new landing page with a focus on social validation. Social validation means that the advertisement references other people who like the service in an effort to pursuade people that if the service works for these people then it might also work for them.

As part of this effort the marketing team wanted to show Amazon Product Reviews for a book that was written about our website. Normally I would just suggest copying a few reviews or taking a screenshot and pasting it, but recently Amazon.com began cracking down on this sort of behavior. My guess is that Amazon.com wants to manage the presentation of the data and get proper attribution links.

Thus, we needed to implement a programmatic way to query Amazon and get an iframe for displaying reviews on our landing page. Documentation for gettting user reviews can be found here. Notice that the link has a “RequestSignature” parameter. It turns out that the primary challenge to getting the api request to work is in calculating this signature. Luckily calculating this signature is well documented and I was able to come up with a solution. I broke out my trusty text editor and came up with this piece of code. Hopefully this saves you a couple minutes of your time so you can go back to drinking coffee and mingling at the water cooler.

  class AmazonProductAdvertisingApiSigner

attr_accessor :url, :secret

def initialize(url:, secret:)
self.url = url
self.secret = secret
end

def sign
host_and_path, params = *self.url.split("?")
params = params.gsub(",","%2C").gsub(":","%3A")
canonical = params.split("&").sort.join("&")
data = ['GET', 'webservices.amazon.com', '/onca/xml', canonical].join("\n")
sha256 = OpenSSL::Digest::SHA256.new
sig = OpenSSL::HMAC.digest(sha256, self.secret, data)
signature = Base64.encode64(sig).strip
signature = signature.gsub("+", "%2B").gsub("=", "%3D")
"#{host_and_path}?#{canonical}&Signature=#{signature}"
end

end

# class usage.
# Note, you must use your own secret and accesskey get them by signing up at the following url
# https://affiliate-program.amazon.com/gp/flex/advertising/api/sign-in.html

signer = AmazonProductAdvertisingApiSigner.new(secret: "1234567890", url: "http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&AssociateTag=mytag-20&Operation=ItemLookup&ItemId=0679722769&ResponseGroup=Images,ItemAttributes,Offers,Reviews&Version=2013-08-01&Timestamp=2014-08-18T12:00:00Z")
signed_url = signer.sign

For those of you developing a rails application this is how I ended up using the class in my controller.

  def book_reviews
# cache result, the result returned lasts 24 hours before needing to be refreshed
cached_iframe_url = Rails.cache.fetch("book_review_iframe_url", expires_in: 23.hours) do
url = "http://webservices.amazon.com/onca/xml?AWSAccessKeyId=XXXXXXXXXXXX&AssociateTag=httpmusicxcom-20&ItemId=0977751228&IdType=ISBN&SearchIndex=Books&Operation=ItemLookup&ResponseGroup=Reviews&Service=AWSECommerceService&Timestamp="
# timestamp format is important must be in UTC
timestamp_str = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
url << timestamp_str
signer = AmazonProductAdvertisingApiSigner.new(secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", url: url)
signed_url = signer.sign
# get the xml from amazon
clnt = HTTPClient.new
body = clnt.get(signed_url).content

# use nogogiri to get the element we are after and pull out the iframe url
xml_doc = Nokogiri::XML(body)
iframe_url = xml_doc.css('IFrameURL').text
raise "not a good url" unless iframe_url.present? and iframe_url =~ /amazon\.com\/reviews\/iframe/i
iframe_url
end

# lastly redirect the user to the iframe.
redirect_to cached_iframe_url

end

Finally put the iframe on the page

  <iframe width="100%" height="600" src="/public_contents/book_reviews" frameborder="0"></iframe>

This page changes but if you want to see an example of the final work, check it out.

I realize i’m mostly talking to myself on this blog but if anyone in on the interwebs finds this useful please leave a comment.