Amazon is the king of making their products accessible via web services, but unfortunately Ruby hasn’t made the list of languages they provide sample client code for. At least not for FWS. So if you’re looking to build an e-commerce site in ruby that uses Fulfillment By Amazon to ship physical products you’ve get to roll your own. Amazon provides two APIs for accessing FWS: REST and SOAP.
If you’re a Ruby developer (and especially Rails) we’ve all been told REST > SOAP. REST is lightweight, simple, etc. So of course we’ll use REST! So I dug in and implemented the Outbound Use Cases for creating a fulfillment order, checking the service status, and canceling an order. And for months it worked fine. Then one day something changed. I’m still not quite sure what changed, but I started getting back 400 Bad Request responses from Amazon. That was it. No details, no explanations, no clue as to what went wrong, just 400 Bad Request. So after wasting way too much time trying to figure out what wasn’t working (with very little help from Aamzon), I decided it was time to throw in the towel and switch to SOAP. SOAP is by nature more verbose. So I figured that even if the problem was on my end, with SOAP I’d get back more useful error messages in the responses. I was right.
So you’re going to do SOAP in ruby, the natural, default option is soap4r. I’ve used soap4r before, and in the past it had done exactly what I needed. Unfortunately, the simplicity of soap4r caused me nothing but pain when trying to work with the FWS SOAP API. Amazon wants (needs) an <evn:Header> in your soap envelope containing 3 pieces of information: Access Key, Timestamp, and Signature. I know that soap4r supports adding extra headers, but it’s really clumsy. So after an hour or so of getting it wrong with soap4r, I decided it was time to look for an alternative.
Enter handsoap! Handsoap does less for you, *a lot less*, but that also means it’s way more flexible. Flexibility is what I needed. In less than 2 hours from finding handsoap, I had a working SOAP client for the call I cared the most about (creating an order), with nice tests to go with it! Enough babbling, time for some code samples:
I’ll be repeating some of what is in the handsoap getting started guide, but you’ll appreciate having it all in one place. (Do yourself a favor and still read through the handsoap getting started guide).
First create config/initializers/amazon_fulfillment_service.rb with the service endpoint:
# wsdl: https://fba-outbound.amazonaws.com/doc/2007-08-02/AmazonFBAOutbound.wsdl AMAZON_FBA_OUTBOUND_SERVICE_ENDPOINT = { :uri => 'https://fba-outbound.amazonaws.com', :version => 1 }
Next, run the generator to create the stubs:
./script/generate handsoap https://fba-outbound.amazonaws.com/doc/2007-08-02/AmazonFBAOutbound.wsdl
Open the file created (probably app/models/amazon_fba_outbound_service.rb)
You’ll want to add your AWSAccessKeyId to the header for all calls:
def on_create_document(doc) # register namespaces for the request doc.alias 'aws', 'http://fba-outbound.amazonaws.com/doc/2007-08-02/' header = doc.find("Header") header.add "aws:AWSAccessKeyId", FWS_CONFIG[:access_key_id] do |s| s.set_attr "env:mustUnderstand", "0" s.set_attr "env:actor", "http://schemas.xmlsoap.org/soap/actor/next" s.set_attr "xmlns:aws", "http://security.amazonaws.com/doc/2007-01-01/" end end
Next let’s look at the simplest of calls: get_service_status
def get_service_status response = invoke('aws:GetServiceStatus') do |message| build_headers(message.document, 'GetServiceStatus') end response.http_response.status end
Returning response.http_response.status makes the method easy to test (just look for 200). The interesting part of that call is the build_headers method. We need to add the Timestamp and Signature to the Header on a per-call basis. Here’s that method:
def build_headers(doc, action, timestamp = nil) @timestamp ||= Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S") + '.000Z' @signature = FwsHelper.aws_encoded(action + @timestamp) header = doc.find("Header") # Add signature header.add "aws:Signature", @signature do |s| s.set_attr "env:mustUnderstand", "0" s.set_attr "env:actor", "http://schemas.xmlsoap.org/soap/actor/next" s.set_attr "xmlns:aws", "http://security.amazonaws.com/doc/2007-01-01/" end # Add timestamp header.add "aws:Timestamp", @timestamp do |s| s.set_attr "env:mustUnderstand", "0" s.set_attr "env:actor", "http://schemas.xmlsoap.org/soap/actor/next" s.set_attr "xmlns:aws", "http://security.amazonaws.com/doc/2007-01-01/" end end
The signature is built by concatenating the action + timestamp, then creating an HMAC-SHA1 digest of it. Here’s my
FwsHelper.aws_encoded
method:
require 'base64' require 'digest/sha1' class FwsHelper def self.aws_encoded(string) digest = OpenSSL::Digest::Digest.new('sha1') b64_hmac = Base64.encode64(OpenSSL::HMAC.digest(digest, FWS_CONFIG[:secret_access_key], string)).strip end end
That should be enough code to get you moving. If I missed something, or you’d like more elaboration on something, tell us about it in the comments and I’ll update the post.

