Amazon MWS SubmitFeed Content-MD5 HTTP header did not match the Content-MD5 calculated by Amazon
Asked Answered
W

4

27

I know this question is not new but all the solution I get for this are in PHP or my issue is different from them.

I am using MWS feed API to submit flat file for Price and Quantity Updates and always get the following error:

the Content-MD5 HTTP header you passed for your feed did not match the Content-MD5 we calculated for your feed


I would like to ask 3 questions here:-

  1. ContentMD5Value parameter is optional as given in doc, but if i not passed that than it will say that you must enter ContentMD5Value.

  2. As in doc the ContentFeed which we are given to Amazon. Amazon create contentMD5 for that file and then compares that contentMD5 value with the contentMD5 value we send to Amazon.
    If both match then OK, otherwise it will throw an error. But if suppose I will not send the file then also the same errors come that MD5 does not match. How is that possible? Which file are they calculating the MD5 for? Because I haven't send the file in ContentFeed.

  3. If I send the contentMD5 in a header as well as parameter and sending the ContentFeed in body, I still get the error.

Note:- I am sending the contentMD5 in a header as well as in a parameters in form using request module and also calculating the signature with that and then pass the contentFeed in body.

I am using JavaScript (Meteor), I calculate the md5 using the crpyto module.
First, I think that my md5 is wrong but then I tried with an online website that will give me the md5 for a file the md5.

for my file is:

MD5 value: d90e9cfde58aeba7ea7385b6d77a1f1e
Base64Encodevalue: ZDkwZTljZmRlNThhZWJhN2VhNzM4NWI2ZDc3YTFmMWU=

The flat file I downloaded from for Price and Quantity Updates:-

https://sellercentral.amazon.in/gp/help/13461?ie=UTF8&Version=1&entries=0&

I calculated the signature also by giving ContentMD5Value while calculating the signature.

FeedType:'_POST_FLAT_FILE_PRICEANDQUANTITYONLY_UPDATE_DATA_'

As, I read documentation for that I passed the MD5-header in headers and also send as parameter.

Amazon doc says:

Previously, Amazon MWS accepted the MD5 hash as a Content-MD5 header instead of a parameter. Passing it as a parameter ensures that the MD5 value is part of the method signature, which prevents anyone on the network from tampering with the feed content.

Amazon MWS will still accept a Content-MD5 header whether or not a ContentMD5Value parameter is included. If both a header and parameter are used, and they do not match, you will receive an InvalidParameterValue error.

I am using the request module for http requests.

I am passing all the required keys, seller id, etc. in form of request module and passing the FeedContent in body.

I tried sending the file as follows:

Method for submitFeed is:-

submitFeed : function(){
    console.log("submitFeedAPI running..");
    app  = mwsReport({auth: {sellerId:'A4TUFSCXD64V3', accessKeyId:'AKIAJBU3FTBCJUIZWF', secretKey:'Eug7ZbaLljtrnGKGFT/DTH23HJ' }, marketplace: 'IN'});
    app.submitFeedsAPI({FeedType:'_POST_FLAT_FILE_PRICEANDQUANTITYONLY_UPDATE_DATA_'},Meteor.bindEnvironment(function(err,response){
        if(err){
            console.log("error in submit feed...")
            console.log(err)
        }
        else{
            console.log("suuccess submit feed....")
            console.log(response);
        }
    }))

Method that call Amazon submitFeedAPI is:-

    var submitFeedsAPI =  function(options, callback){
        console.log("submitFeedsAPI running...");
    var fileReadStream = fs.createReadStream('/home/parveen/Downloads/test/testting.txt');
        var contentMD5Value = crypto.createHash('md5').update(file).digest('base64');

    var reqForm = {query: {"Action": "SubmitFeed", "MarketplaceId": mpList[mpCur].id, "FeedType":options.FeedType,"PurgeAndReplace":false,"ContentMD5Value":contentMD5Value}}; 
            mwsReqProcessor(reqForm, 'submitFeedsAPI', "submitFeedsAPIResponse", "submitFeedsAPIResult", "mwsprod-0000",false,file, callback);
    }


also try

    var fileReadStream = fs.createReadStream('/home/parveen/Downloads/test/testting.txt');
    var base64Contents = fileReadStream.toString('base64');
    var contentMD5Value = crypto.createHash('md5').update(base64Contents).digest('base64');

mwsReqProcessor function is as below:-

 mwsReqProcessor = function mwsReqProcessor(reqForm, name, responseKey, resultKey, errorCode,reportFlag,file, callback) {

        reqOpt = {
            url: mwsReqUrl,
            method: 'POST',
            timeout: 40000,
            body:{FeedContent: fs.readFileSync('/home/parveen/feedContentFile/Flat.File.PriceInventory.in.txt')},
            json:true,
            form: null,
            headers: {
                // 'Transfer-Encoding': 'chunked',
                //'Content-Type': 'text/xml',
               // 'Content-MD5':'ZDkwZTljZmRlNThhZWJhN2VhNzM4NWI2ZDc3YTFmMWU=',
                // 'Content-Type': 'text/xml; charset=iso-8859-1'
                'Content-Type':'text/tab-separated-values;charset=UTF-8'
            },
        }
        reqOpt.form = mwsReqQryGen(reqForm);
        var r = request(reqOpt, function (err, res, body){
            console.log(err)
            console.log(res)
        })
        // var form  = r.form();
        //form.append('FeedContent',fs.createReadStream('/home/parveen/feedContent//File/Flat.File.PriceInventory.in.txt'))
    }

Method for mwsReqQryGen generation:-

mwsReqQryGen = function mwsReqQryGen(options) {
    var method     = (options && options.method) ? ('' + options.method) : 'POST',
        host       = (options && options.host)   ? ('' + options.host)   : mwsReqHost,
        path       = (options && options.path)   ? ('' + options.path)   : mwsReqPath,
        query      = (options && options.query)  ? options.query         : null,


        returnData = {
          "AWSAccessKeyId": authInfo.accessKeyId,
          "SellerId": authInfo.sellerId,
          "SignatureMethod": "HmacSHA256",
          "SignatureVersion": "2",
          "Timestamp": new Date().toISOString(),
           "Version":"2009-01-01",
        },
        key;

    if(query && typeof query === "object")
      for(key in query)
        if(query.hasOwnProperty(key)) returnData[key] = ('' + query[key]);

    if(authInfo.secretKey && method && host && path) {

      // Sort query parameters
      var keys = [],
          qry  = {};

      for(key in returnData)
        if(returnData.hasOwnProperty(key)) keys.push(key);

      keys = keys.sort();
      for(key in keys)
        if(keys.hasOwnProperty(key)) qry[keys[key]] = returnData[keys[key]];
      var sign = [method, host, path, qs.stringify(qry)].join("\n");
      console.log("..................................................")
      returnData.Signature = mwsReqSignGen(sign);

    }
//console.log(returnData); // for debug

return returnData;

};

I also tried with following:-

reqOpt = {
    url: mwsReqUrl,
    method: 'POST',
    timeout: 40000,
    json:true,
    form: null,
    body:  {FeedContent: fs.createReadStream('/home/parveen/feedContentFile/Flat.File.PriceInventory.in.txt')},
    headers: {
        // 'Transfer-Encoding': 'chunked',
        //'Content-Type': 'text/xml',
       // 'Content-MD5':'ZDkwZTljZmRlNThhZWJhN2VhNzM4NWI2ZDc3YTFmMWU=',
        //   'Content-Type': 'text/xml; charset=iso-8859-1'
    },
}

I also tried without JSON and directly send the file read stream in the body, i.e:

reqOpt = {
    url: mwsReqUrl,
    method: 'POST',
    timeout: 40000,
    form: null,
    body:  fs.createReadStream('/home/parveen/feedContentFile/Flat.File.PriceInventory.in.txt'),
    headers: {
        // 'Transfer-Encoding': 'chunked',
        //'Content-Type': 'text/xml',
     //   'Content-MD5':'ZDkwZTljZmRlNThhZWJhN2VhNzM4NWI2ZDc3YTFmMWU=',
        //   'Content-Type': 'text/xml; charset=iso-8859-1'
    },
}

But same error comes every time:

the Content-MD5 HTTP header you passed for your feed did not match the Content-MD5 we calculated for your feed

I want to know where I am doing wrong or what is the right way to submit feed API and sending the file using request module.

I also tried with the code given on MWS to generate the MD5 but same error occurred each time.

My .txt file as follows:

sku price   quantity
TP-T2-00-M      2

Any help is much appreciated

Williams answered 21/10, 2016 at 15:8 Comment(11)
You did not post the code you are using for hashing the data. Did you try: crypto.createHash('md5').update(data).digest('base64')?Intercross
@Intercross thanks for reply , yes i am using the same for creating the MD5 but as i written for surety that might be my MD5 is not right so i generate the MD5 via the java code given in Amazon mws doc. link for that is :- docs.developer.amazonservices.com/en_IN/dev_guide/DG_MD5.html but still getting the same error the md5 and base64 encoded content value generated by Amazon code is -: "kRbUe45m80Ak/N2zbtN20A=="Williams
Without your code (or the post stream), it is not clear exactly what you are sending Amazon. I am no expert in MWS, but it is quite evident that you are base-64 encoding the string value of the md5 hash instead of the raw (binary) hash. In addition, I was unable to create a file with the posted hash from the txt you posted (probably related to whitespace differences). Can you post a string version of it of upload it somewhere?Intercross
Do you have to use MD5? Or can you use something newer such as SHA? I know the MWS Infrastructure uses a little bit older version of signatures but I'm fairly certain v4 signatures are accepted.Nibelungenlied
@Intercross thanks for giving your precious time to my question actually the base64 encode of content md5 for my file from code is:- "FEGnkJwIfbvnzlmIG534uQ==". You need to just create a new .txt file and enter the values as tab separated as given above in .txt file example. The full code is as below:- var fileReadStream = fs.createReadStream('/home/parveen/Downloads/test/testting.txt'); var file = fileReadStream.toString('base64'); var contentMD5Value = crypto.createHash('md5').update(file).digest('base64');Williams
Hey do you got solution? I am having same problem with POST_FBA_INBOUND_CARTON_CONTENTS in C#?Holub
@Meghashah I got the solution as accepted answer is listed below. If you facing the issue than first you need to check the feed you are submitting is correct or not than check whether you are sending that file in form data or not because the signature required parameter are sent as query string and the feed is submitted in the Form-Data. ThanksWilliams
@Parveenyadav thanks for your notice. I also got the solution. But still want to ask you, when you submit this using API, are you able to see your data in their front?Holub
@Meghashah sorry but i didn't get you what you want to say are you able to see your data in their front?Williams
@Parveenyadav I mean when you submit your file, API gives you success. But if you want to check your file content in amazon seller login when they are giving option for seller to upload file. Are you able to check your data their? In my case I have submitted carton information and got success but I am unable to see that information in front where amazon is giving option to upload file.Holub
@Meghashah yes when you submit any feed you can check the file submission in Amazon upload file section and you can check there the status that is first submit than pending and than change to done. But i guess you can only see that if you submit feed via flat file not by xml.Williams
W
4

finally i got the solution as Ravi said above. Actually there are few points i want to clear here for you all who are facing the same issue:-

  1. Amazon marketplace API doc is not giving proper information and example. Even i guess the documentation is not updated . As in doc they said that ContentMD5Value parameter value is optional on this page

    You can check there they clearly mention that the field is not required but if you not pass than they gives the error that you must pass content MD5 value.

    So that is wrong. ContentMD5 is required attribute.

  2. They said in the same doc that you need to send file data weather its a xml or flat-file in the field key name i.e. FeedContent.

    But that is also not needed you can send the file with any name no need to give FeedContent key for the file you just need to send the file in stream.

  3. They will give the same error of contentMD5 not match weather you send file or not because if they not found file than the contentMD5 you send will not match to that. SO if you are getting the ContentMD5 not match error than check the following:-

    1. Check that you are generating the right MD5 code for your file you can check whether you are generating the right code or not by there java code they given on doc . You can get that from this link

    2. Don't trust on online websites for generating the MD5 hash and base64 encoding.

    3. If your MD5 is matched with the MD5 generated from Java code they given than one thing is clear that your MD5 is right so no need to change on that.

    4. Once your MD5 is correct and after that also if you get the same error that is:-

      Amazon MWS SubmitFeed Content-MD5 HTTP header did not match the Content-MD5 calculated by Amazon

ContentMD5 not matched .Than you need to check only and only you file uploading mechanism. Because now the file you are sending to Amazon is not either correct or you are not sending it in the right way.

Check for file upload

For checking whether or not you are sending the right file you need to check with following:-

  1. You need to send the required parameters like sellerId, marketplaceId, AWSAccessKey etc. as query params.

  2. You need to send the file in the form-data as multipart , if you are using the request module of node.js than you can see the above code given by Ravi.

  3. you need to set the header as only:-

    'Content-Type': 'application/x-www-form-urlencoded'

No need to send the header as chunked or tab separated etc because i don't need them any more they are even confuse me because somewhere someone write use this header on other place someone write use this header. So finally as i am abel to submit this API i didn't need any of the header rather than application/x-www-form-urlencoded.

Example:-

reqOpt = {
   url: mwsReqUrl,
   method: 'POST',
   formData: {
      my_file: fs.createReadStream('file.txt')
  },
  headers: {
     'Content-Type': 'application/x-www-form-urlencoded'
  },
qs: { }// all the parameters that you are using while creating signature.

Code for creating the contentMD5 is:-

var  fileData= fs.readFileSync('/home/parveen/Downloads/test/feed.txt','utf8');

var contentMD5Value = crypto.createHash('md5').update(fileData).digest('base64');

As i am facing the issue that is because i am using form and form-data simultaneously via request module so i convert my form data with qs(query string) and file in form-data as multipart.

So in this way you can successfully submit the API for submit feed.

Williams answered 11/11, 2016 at 8:45 Comment(0)
I
3

Amazon requires the md5 hash of the file in base64 encoding.

Your code:

var fileReadStream = fs.createReadStream('/path/to/file.txt');
var file = fileReadStream.toString('base64'); //'[object Object]'
var contentMD5Value = crypto.createHash('md5').update(file).digest('base64');

wrongly assumes that a readStream's toString() will produce the file contents, when, in fact, this method is inherited from Object and produces the string '[object Object]'.

Base64-encoding that string always produces the 'FEGnkJwIfbvnzlmIG534uQ==' that you mentioned.

If you want to properly read and encode the hash, you can do the following:

var fileContents = fs.readFileSync('/path/to/file.txt'); // produces a byte Buffer
var contentMD5Value = crypto.createHash('md5').update(fileContents).digest('base64'); // properly encoded

which provides results equivalent to the following PHP snippet:

$contentMD5Value = base64_encode(md5_file('/path/to/file.txt', true));
Intercross answered 7/11, 2016 at 8:26 Comment(9)
thanks for your reply but as i use your code that will produce a byte buffer it will throw an exception i.e:- Exception while invoking method 'submitFeedAPI' TypeError: Not a string or bufferWilliams
This is not directly relate to the code in this answer. I suggest you to edit your question to provide a reproduction of your situation, including all of the required code.Intercross
i have attched the code for generating and sending content MD5 above please have a look and please let me know you need any other code to understand ThanksWilliams
Where are you calling submitFeedAPI in the code that you posted? You need to isolate the issues (you may have multiple issues). First, your md5 generation is clearly wrong, as I explained in the post. Let's take care of that first. If you want to test using your file, please upload it somewhere, as I am unable to recreate it exactly. Are you unable to get a valid base64-encoded md5 using my code?Intercross
thanks again now this time i have added all the code i am using from starting to end please check and let me know where i am doing wroong. ThanksWilliams
As i using your code you gave above for properly read and encode the hash, it will gives an exception becuase in "var file = fileReadStream.read();" in file it gives null. ThanksWilliams
This is due to me using the async API. Please see the edit.Intercross
i have already check with readFileSync in my previous edit i have also mention that in previous edit but the same error occures wheather i use readStream or readFileSync i have add the complete code for call please see the edit thanksWilliams
Let us continue this discussion in chat.Intercross
N
2

Hey sorry for late reply but why don't you try to send the file in multipart in the form-data request and other queryStrings in 'qs' property of request module. You can submit the request as follows:-

 reqOpt = {
   url: mwsReqUrl,
   method: 'POST',
   formData: {
      my_file: fs.createReadStream('file.txt')
  },
  headers: {
     'Content-Type': 'application/x-www-form-urlencoded'
  },
  qs: { 
     AWSAccessKeyId: '<your AWSAccessKeyId>',
     SellerId: '<your SellerId>',
     SignatureMethod: '<your SignatureMethod>',
     SignatureVersion: '<your SignatureVersion>',
     Timestamp: '<your Timestamp>',
     Version: '<your Version>',
     Action: 'SubmitFeed',
     MarketplaceId: '<your MarketplaceId>',
     FeedType: '_POST_FLAT_FILE_PRICEANDQUANTITYONLY_UPDATE_DATA_',
     PurgeAndReplace: 'false',
     ContentMD5Value: '<your file.txt ContentMD5Value>',
     Signature: '<your Signature>'
 }
}

request(reqOpt, function(err, res){

})
Nullipore answered 11/11, 2016 at 7:4 Comment(2)
Thanks for your reply i am facing this problem from last few days. Thanks for you reply . This works for me.Williams
Thanks @Parveen yadav, good to hear that it worked for you too.Nullipore
G
0

Probably, I'm too late, but here are key points for C#:

1) Multipart form-data didn't work at all. Finished with the following (simplified):

HttpContent content = new StringContent(xmlStr, Encoding.UTF8, "application/xml");
HttpClient client = new HttpClient();
client.PostAsync(query, content)

2) About query:

UriBuilder builder = new UriBuilder("https://mws.amazonservices.com/");
NameValueCollection query = HttpUtility.ParseQueryString(builder.Query);

query["AwsAccessKeyId"] = your_key_str;
query["FeedType"] = "_POST_ORDER_FULFILLMENT_DATA_";
... other required params
query["ContentMD5Value"] = Md5Base64(xmlStr);

builder.Query = query.ToString();
query = builder.ToString();

3) About Md5base64

public static string Md5Base64(string xmlStr)
{
  byte[] plainTextBytes = Encoding.UTF8.GetBytes(xmlStr);
  MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider();
  byte[] hash = provider.ComputeHash(plainTextBytes);
  return Convert.ToBase64String(hash);
}
Gossipmonger answered 5/10, 2018 at 19:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.