ASDA (Walmart) API

After taking a look at the Tesco API, the only next logical step is uncovering the ASDA API. Unfortunately ASDA arn’t as open as Tesco as they don't offer any public APIs that anybody can use. In this post we’ll take a look under the hood of the ASDA Android app to try and extract a usable API from it.

If you’re not bothered about the technicals jump straight to the example Tesco/ASDA product price comparison.

First things first, download the ASDA app to your Android device (or emulator). We need to grab the APK so we can decompile it, if your Android device is rooted you can simply copy it from the /system/app directory. If your device is not rooted then I’d recommend apkExtractor which can extract the APK from your system so you can e-mail it yourself. Now we have our APK file, lets decompile it. Fortunately dex2jar and Java Decompiler makes this very easy for us – I won’t go into any detail on how to do this but there’s tons of articles around the web explaining what to do.

At the request of ASDA/Walmart I have removed all references to any API and secret keys.

Open up the .jar file in the Java Decompiler GUI and you’ll see the source code to the ASDA app. The bulk of the code that does the HTTP requests is within com.asda.android.service.base.AsdaServiceImpl.class, a lot of variable definitions are in AsdaService.class – goodies! From here we will find our API endpoint: https://api-groceries.asda.com/api/. Let’s hook up a proxy and sniff the request/response for the login method:

POST /api/user/login?apikey=XXXXXXX&sig=XXXXXXXXX&requestorigin=android HTTP/1.1
Content-Type: application/x-www-form-urlencoded

email=a%40b.com&password=12345

The e-mail and address must be a valid ASDA.com account. We can see an API key and a "signature" being sent. If you attempt to login with the same details again the signature changes, suggesting it is time based – lets see what’s going on. Searching through the source code we see this method: public void logIn(String paramString1, String paramString2, AsyncCallback paramAsyncCallback) { }. If you eventually follow this code through createBaseUrl gets called which in turn calls this: generateSignature(AsdaServiceSettings.API_KEY, AsdaServiceSettings.SECRET_KEY) – ah, now we’re getting somewhere. Our API key: XXXXXXXXX, our “secret”: XXXXXXXXX.

Let’s take a look at the generateSignature method:

public String generateSignature(String paramString1, String paramString2)
{
    String str1 = this.mTimeManager.getCurrentTimeSeconds();
    this.mKeyGenBuffer.delete(0, this.mKeyGenBuffer.length());
    this.mKeyGenBuffer.append(paramString1).append(paramString2).append(str1);
    String str2 = this.mKeyGenBuffer.toString();
    return this.mStringHasher.createHashedString(str2);
}

As I suspected, we’re creating the hash with the current time. So it looks like our hash format is {api_key}{secret}{time} – nice and easy to reproduce. Taking a dive into the StringHasher class we see it’s a simple SHA-256 hash from Java MessageDigest. The TimeManager class simply gets the current time in seconds since Epoch.

So now we know how to create a signature, lets hook some code up and give it a go. I extracted the signature generating methods into its own Java app. Since my end goal was to build a C# client here's the ported code:

public string GenerateSignature()
{
    TimeSpan time = (DateTime.UtcNow - new DateTime(1970, 1, 1));
    string toHash = String.Concat("XXXXXXXXX" /* api key */, "XXXXXXXXX" /* "secret" */, time.TotalSeconds.ToString("F0"));

    SHA256Managed hasher = new SHA256Managed();
    var hash = hasher.ComputeHash(System.Text.Encoding.UTF8.GetBytes(toHash));

    return String.Concat(hash.Select(b => b.ToString("x2")).ToArray());
}

Simples. To test it out do a HTTP GET to: https://api-groceries.asda.com/api/user/login?apikey=XXXXXXXXX&sig={generated_signature}&requestorigin=android&[email protected]&password=123 (with your ASDA.com user + pass) – et voilà, we get a success message with the logged in users details:

{
  "statusCode": "0",
  "statusMessage": "The API Login was executed successfully",
  "errors": [

  ],
  "custType": "",
  "basketId": "2006083786",
  "postCode": "XXXX",
  "storeId": "4661",
  "dateofBirth": "",
  "jobTitle": "",
  "bizTradingName": "",
  "firstName": "Paul",
  "lastName": "XXXXX",
  "slotDate": "",
  "deliveryStartTime": "",
  "deliveryEndTime": "",
  "email": "[email protected]",
  "middleName": "",
  "doorStepTime": "0 seconds",
  "deliveryPrice": "",
  "bizType": "",
  "deliveryOption": "homedelivery"
}

The response for the login command also contains a few cookies you need to pass on for any future commands:

DYN_USER_CONFIRM=4a1adc8aaf52662b91fbe4640b96d037;
DYN_USER_ID=1455357;
SSL_SESSION_ID=2221c2dfe33e43833daf0d9d8260ebbc;
KNOWN_USER=YES;
STORE_ID=4661
MTEP_EXISTING_SESSION=yes;
JSESSIONID" =jEcy9m5I-zT9S-vTWIuydw**.oses4069-atg14;

and now searching for products becomes trivial, it’s just a GET request to https://api-groceries.asda.com/api/items/search?apikey=XXXXXXXXX&sig={generated_signature}&requestorigin=android&keyword={keyword}&pagenum=1.

Example response (with keyword milk):

{
  "statusCode": "0",
  "statusMessage": "The API Item Search was executed successfully",
  "errors": [

  ],
  "keyword": "milk",
  "totalResult": "250",
  "currentPage": "1",
  "resultsStartIndex": "1",
  "resultsEndIndex": "25",
  "items": [
    {
      "deptId": "1214921923736",
      "deptName": "Dairy, Eggs & Chilled",
      "imageURL": "http:\/\/i.groceries.asda.com:80\/\/g\/680\/544\/5051413680544_6000_IDShot_1.jpeg",
      "asdaSuggest": "No",
      "cin": "3894487",
      "maxQty": "24.0",
      "id": "910000582969",
      "wasPrice": "",
      "availability": "A",
      "pricePerWt": "Each",
      "price": "\u00a31.00",
      "promoDetail": "2 FOR \u00a32.00",
      "name": "ASDA Fresh 1% Fat Milk",
      "promoType": "New",
      "promoId": "ls68347",
      "weight": "4PT",
      "productURL": "https:\/\/api-groceries.asda.com:443\/api\/items\/view?itemid=910000582969",
      "pricePerUOM": "44.0p\/lt"
    },
    {
      "deptId": "1214921923736",
      "deptName": "Dairy, Eggs & Chilled",
      "imageURL": "http:\/\/i.groceries.asda.com:80\/\/g\/337\/087\/20337087_6000_IDShot_1.jpeg",
      "asdaSuggest": "No",
      "cin": "165468",
      "maxQty": "12.0",
      "id": "20504",
      "wasPrice": "\u00a31.53",
      "availability": "A",
      "pricePerWt": "Each",
      "price": "\u00a31.00",
      "promoDetail": "2 FOR \u00a32.00",
      "name": "ASDA Fresh Milk Semi Skimmed",
      "promoType": "No promo",
      "promoId": "ls68347",
      "weight": "4PT",
      "productURL": "https:\/\/api-groceries.asda.com:443\/api\/items\/view?itemid=20504",
      "pricePerUOM": "44.0p\/lt"
    }
  ]
}

Combined with the Tesco API you could create a powerful price comparision app.

I hope this helps somebody. I would also suspect that this is pretty much the same for Walmart too, seeing as Walmat own ASDA. There’s also a bunch of references to Walmart throughout the source code so take a delve in.