1

I am trying to link up with Walmart.io API to get some data from their resources. But I am stuck up in the first phase.

According to Walmart.io Quick Start Doc (https://walmart.io/docs/affiliate/quick-start-guide) I am supposed to follow following steps:

  1. Create an account with Walmart.io
  2. Create an application for Web Application
  3. Generate a certificate ( According to their guide there should be some feature to autogenerate the certificate, but I didn't find it)
  4. Upload public key to the application
  5. We will get consumer id and key version using which along with private key, we can make a request. We need to add additional headers that includes Signature and Timestamp too.

So, I did everything, but it still isn't working.

I am using Open SSL to generate private and public key as suggested by them: https://walmart.io/key-tutorial I tried avoiding -des3 so that it doesn't ask me for passphrase too, but it didn't work either.

Here is the script I tried with

curl --location --request GET 'https://developer.api.walmart.com/api-proxy/service/affil/product/v2/taxonomy' \
--header 'WM_SEC.KEY_VERSION: 2' \
--header 'WM_CONSUMER.ID: <Consumer_ID>' \
--header 'WM_CONSUMER.INTIMESTAMP: 1594389945813' \
--header 'WM_SEC.AUTH_SIGNATURE: W5PEHIew3LsnATk0zxJddeo416YEpMIjvk1b7lW9VMIZFx55erc/5df/FK9UtS5i48q057oASo0AX3SDd2hx+QSeyiX3FtLAgAgiZnGqQ6nJndySWgL5ih/GaUTXIC6dd048GFEZlC6axXdGoTWNzX9P0n/2DwLF9EtvMjdvjB1kum0z2xKz/lQGlvnjVkGK9sZdSUa5rfgxKSPi7ix+LRIJWYwt6mTKUlGz2vP1YjGcZ7gVwAs9o8iFC//0rHUWFwaEGrT0aZJtS7fvSFtKj5NRfemX4fwRO4cgBRxPWy9MRooQwXPmKxRP75PxHKTerv8X6HvRo0GdGut+2Krqxg==' \

And the response I get is

{
    "details": {
        "Description": "Could not authenticate in-request, auth signature :  Signature verification failed: affil-product, version: 2.0.0, env: prod",
        "wm_svc.version": "2.0.0",
        "wm_svc.name": "affil-product",
        "wm_svc.env": "prod"
    }
}

Hope someone gives me some insight into this problem.

Thanks in advance

3 Answers3

1

Here is a full example based on Abiral's post above:

<?php

/**
 * Sample script to sign and send a request to the Walmart Affiliate Marketing API.
 *
 * https://walmart.io/docs/affiliate/introduction
 *
 * Usage:
 *   1. Fill out the required variables at the top of this script.
 *   2. Install dependencies via composer install.
 *   3. Run via php index.php or by opening this script in a browser.
 *
 * Acknowledgements:
 *   Abiral Neupane at https://stackoverflow.com/a/62847241/1120652
 *   @gorenstein at https://gitter.im/IO-support/community?at=5f2e5d2051bb7d3380d9b58b
 */

include './vendor/autoload.php';

use \GuzzleHttp\Client;

/**
 * Create an account at Walmart.io. Then create an application. Then follow the
 * steps at https://walmart.io/key-tutorial to create a set of keys. Upload
 * the public key (its contents start with BEGIN PUBLIC KEY) into the
 * production environment of the application that you created.
 */
$consumer_id = 'Paste here the consumer id that you will see in your application details after pasting the public key';
$key = 'Paste here the private key. Full, including BEGIN and END PRIVATE KEY lines.';

$version = '1';
$timestamp = round(microtime(true) * 1000);
$message = $consumer_id . "\n" . $timestamp . "\n" . $version . "\n";

$pkeyid = openssl_pkey_get_private($key);
openssl_sign($message, $signature, $pkeyid, OPENSSL_ALGO_SHA256);
$signature = base64_encode($signature);
openssl_free_key($pkeyid);

$api = 'https://developer.api.walmart.com';
$product_resource = 'api-proxy/service/affil/product/v2/items/316226539';
$client = new Client(['base_uri' => $api]);
$response = $client->get($product_resource, [
  'headers' => [
    'WM_SEC.KEY_VERSION' => $version,
    'WM_CONSUMER.ID' => $consumer_id,
    'WM_CONSUMER.INTIMESTAMP' => $timestamp,
    'WM_SEC.AUTH_SIGNATURE' => $signature,
  ]
]);

print_r(json_decode($response->getBody()->__toString()));

I published the above at https://github.com/juampynr/walmart-api-v2-php

Juampy NR
  • 2,618
  • 1
  • 25
  • 21
0

I've had this issue before, it looks like the format of the data you are trying to sign is incorrect.

In node, the content of the template string should look like this: ${consumerId}\n${timeStamp}\n${keyVersion}\n

C. S.
  • 805
  • 6
  • 6
  • Actually I did that too `$message = $this->consumer_id.'\n'.$timestamp.'\n'.$this->key_version.'\n';` Check this for full code: https://pastebin.com/rD1CtQ5f – Abiral Neupane Jul 10 '20 at 18:10
  • I noticed your version is 2; are you using the correct key version? – C. S. Jul 10 '20 at 18:12
  • other than that, what are you using to create the signature? – C. S. Jul 10 '20 at 18:16
  • It was 2 earlier when I was adding this question, but now it is changed to 4 because I made changes to public key: https://nimb.ws/F7kfCv – Abiral Neupane Jul 10 '20 at 18:16
  • For generating signature, I am using "phpseclib\Crypt\RSA" package. Is there any other library you recommend? – Abiral Neupane Jul 10 '20 at 18:17
  • not too familiar with php or the libraries, but to create the token in node was just < 13 lines of code. is that library you'e using signing with a rsa 256 algo? – C. S. Jul 10 '20 at 22:20
  • Yeah it is. Can you share signing code done with node. I will see if I can find something – Abiral Neupane Jul 11 '20 at 02:37
  • @AbiralNeupane https://gist.github.com/combizs/66f753de6535a5063e76d0a608aa96df – C. S. Sep 18 '20 at 20:45
0

Turns out it was issue with generated Signature (That explains why it worked after I changed the script.

Thus here is the script that worked fine:

<?php

use GuzzleHttp\Psr7;
use GuzzleHttp\Exception\RequestException;

class Walmart{

    private $host;

    private $consumer_id;

    private $private_key_file;

    private $headers;

    private $sec_key_version;

    private $client;

    private $options;

    public function __construct($config){
        $this->host             = $config['host'];
        $this->consumer_id      = $config['consumer_id'];
        $this->private_key_file = $config['private_key_file'];
        $this->sec_key_version  = $config['sec_key_version'];

        $this->options = array();
        
        $this->client           = new GuzzleHttp\Client();
    }
    
    public function lookup_product($publisher_id='', $ids='', $upc='', $format='json'){
        $this->load_options();

        $url_params = array(
            'format' => $format,
        );

        if($publisher_id){
            $url_params['publisher_id'] = $publisher_id;
        }

        if($ids){
            $url_params['ids'] = $ids;
        }

        if($upc){
            $url_params['upc'] = $upc;
        }

        $query = http_build_query($url_params);

        $url = $this->host . '/product/v2/items?'.$query;
        try {
            $res = $this->client->request('GET', $url, $this->options);
            $body = $res->getBody();
            if($res->getStatusCode() == 200){
                return $this->response(false, json_decode($body, true));
            }else{
                return $this->response(array(
                    'title' => 'Unable to get products',
                    'stack' => $body,
                ));
            }
        } catch (RequestException $e) {
            $err = Psr7\str($e->getRequest());

            if ($e->hasResponse()) {
                $err .= Psr7\str($e->getResponse());
            }

            return $this->response(array(
                'title' => 'Unable to get products',
                'stack' => $err,
            ));
        }
    }

    private function load_options(){
        $timestamp = time()*1000;
        $this->options = array(
            'debug' => (defined("DEBUG") && DEBUG) ? true: false,
            'headers' => array(
                'WM_SEC.KEY_VERSION'        => $this->sec_key_version,
                'WM_CONSUMER.ID'            => $this->consumer_id,
                'WM_CONSUMER.INTIMESTAMP'   => $timestamp,
                'WM_SEC.AUTH_SIGNATURE'     => $this->get_signature($timestamp),
            )
        );
    }

    private function get_signature($timestamp){

        $message = $this->consumer_id."\n".$timestamp."\n".$this->sec_key_version."\n";

        $pkeyid = openssl_pkey_get_private("file://".$this->private_key_file);

        openssl_sign($message, $signature, $pkeyid, OPENSSL_ALGO_SHA256);

        $signature = base64_encode($signature);

        openssl_free_key($pkeyid);

        return $signature;
    }

    private function response($err, $data=false){
        return array(
            'error' => $err,
            'data' => $data,
        );
    }
}

Note: It uses guzzlehttp/guzzle library for HTTP Request

  • To get this working with phpseclib you'd probably have needed to have done `$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);`. As http://phpseclib.sourceforge.net/rsa/intro.html notes, PKCS1 padding is more common, albeit less secure than OAEP. – neubert Jul 11 '20 at 21:42