Signing requests

For the security of data and system, all requests posted to the server must be cryptographically signed.

To ensure that calls to the API cannot be recorded and replayed, one of the parameters getting signed is a timestamp.

More specifically, the signature parameter required on all calls is the HMAC of the request string and your API key with the SHA256 digest algorithm.

API key

Each user will have a unique API key when calling an API, which is used to sign the request. The API key for a user is created with the creation of the user ID and can be found under {AccountName} -> Manage Users -> Add User.

❗️

Security of the API key

You must ensure the security of your API key and do not share the API key with any third party.

Computing the signature parameter

The string to sign is...

  • the concatenated result of all request parameters
  • ordered by name
  • including optional parameters
  • and excluding the signature parameter

Names and values must be URL encoded according to RFC 3986 standard, concatenated with the character '='. Each parameter set (name=value) must be separated with the character '&'.

The following is the reference implementation PHP.

<?php

// Pay no attention to this statement.
// It's only needed if timezone in php.ini is not set correctly.
date_default_timezone_set("UTC");

// The current time. Needed to create the Timestamp parameter below.
$now = new DateTime();

// The parameters for the GET request. These will get signed.
$parameters = array(
    // The ID of the user making the call.
    'UserID' => '[email protected]',

    // The API version. Currently must be 1.0
    'Version' => '1.0',

    // The API method to call.
    'Action' => 'GetBrands',

    // The format of the result.
    'Format' => 'XML',

    // The current time in ISO8601 format
    'Timestamp' => $now->format(DateTime::ISO8601)
);

// Sort parameters by name.
ksort($parameters);

// URL encode the parameters.
$encoded = array();
foreach ($parameters as $name => $value) {
    $encoded[] = rawurlencode($name) . '=' . rawurlencode($value);
}

// Concatenate the sorted and URL encoded parameters into a string.
$concatenated = implode('&', $encoded);

// The API key for the user as generated in the Seller Center GUI.
// Must be an API key associated with the UserID parameter.
$api_key = 'b1bdb357ced10fe4e9a69840cdd4f0e9c03d77fe';

// Compute signature and add it to the parameters.
$parameters['Signature'] =
    rawurlencode(hash_hmac('sha256', $concatenated, $api_key, false));

If you want to verify the above reference, replace the timestamp with the following value:

'Timestamp' => '2015-07-01T11:11:11+00:00'

You should get the following entries in $parameters.

Action=GetBrands
Format=XML
Timestamp=2015-07-01T11:11:11+00:00
[email protected]
Version=1.0
Signature=3ceb8ed91049dfc718b0d2d176fb2ed0e5fd74f76c5971f34cdab48412476041

Then, to make the GET request in PHP, you would write something like this:

<?php

// ...continued from above

// Replace with the URL of your API host.
$url = "https://api.sg.ali-lazada.com/?";

// Build Query String
$queryString = http_build_query($parameters, '', '&', PHP_QUERY_RFC3986);

// Open cURL connection
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url."?".$queryString);

// Save response to the variable $data
curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$data = curl_exec($ch);

// Close Curl connection
curl_close($ch);

If you are more familiar with other programming languages, here are implementations in...

Signing in Java SDK

When developing in java SDK, you do not need to sign every request. The SDK signs them for you.
All you need to do is simply initializing the global client.

//init global default client with venture URL, your email and API key
LazadaClient.init("https://api.sellercenter.lazada.com.my/","[email protected]", "3aac65392ce5ebdfa7eadf4933c9316a4810581e");

//then forget all about signing, just fire your request

//Example
//1. query order by ID
//init request with orderId
GetOrder request = new GetOrder(6642038L);
//fire request and handle result Order
try {
  GetOrderResponse response = request.execute();
  System.out.println(response.getBody());
} catch (LazadaException e) {
  System.out.println(e.getResponseStr());
}

//2. upload image
//construct request with local file
File image = new File("/Users/yucheng/Desktop/google-search.png");
UploadImage uploadImage = new UploadImage(image);
try {
  ModifyImageResponse response = uploadImage.execute();
  System.out.println("New Url: " + response.getBody().getImage().getUrl());
  System.out.println("HashCode: " + response.getBody().getImage().getCode());
} catch (LazadaException e) {
  System.out.println(e.getResponseStr());
}

API call in Java

/*
  * Sample Interface for SellerCenter API
  */
package com.rocket.sellercenter;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.text.DateFormat;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class SellercenterAPI {
  private static final String ScApiHost = "http://api.sellercenter.net/"; 
  private static final String HASH_ALGORITHM = "HmacSHA256";
  private static final String CHAR_UTF_8 = "UTF-8";
  private static final String CHAR_ASCII = "ASCII";
  public static void main(String[] args) {
    Map<String, String> params = new HashMap<String, String>();
    params.put("UserID", "[email protected]");
    params.put("Timestamp", getCurrentTimestamp());
    params.put("Version", "1.0");
    params.put("Action", "ProductUpdate");
    final String apiKey = "55f86f79f3b4388507aba8c21a7bfd0d25626551";
    final String XML = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?
      ><Request><Product><SellerSku>4105382173aaee4</SellerSku><Price>12</Price></Product></Request>";
      final String out = getSellercenterApiResponse(params, apiKey, XML); // provide XML as an empty string
    when not needed
      System.out.println(out); // print out the XML response
  }

  /**
  * calculates the signature and sends the request
  *
  * @param params Map - request parameters
  * @param apiKey String - user's API Key
  * @param XML String - Request Body
  */
  public static String getSellercenterApiResponse(Map<String, String> params, String apiKey, String XML) {
    String queryString = "";
    String Output = "";
    HttpURLConnection connection = null;
    URL url = null;
    Map<String, String> sortedParams = new TreeMap<String, String>(params);
    queryString = toQueryString(sortedParams);
    final String signature = hmacDigest(queryString, apiKey, HASH_ALGORITHM);
    queryString = queryString.concat("&Signature=".concat(signature));
    final String request = ScApiHost.concat("?".concat(queryString));
    try {
      url = new URL(request);
      connection = (HttpURLConnection) url.openConnection();
      connection.setDoOutput(true);
      connection.setDoInput(true);
      connection.setInstanceFollowRedirects(false);
      connection.setRequestMethod("POST");
      connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
      connection.setRequestProperty("charset", CHAR_UTF_8);
      connection.setUseCaches(false);
      if (!XML.equals("")) {
        connection.setRequestProperty("Content-Length", "" + Integer.toString(XML.getBytes().length));
        DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
        wr.writeBytes(XML);
        wr.flush();
        wr.close();
      }
      String line;
      BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
      while ((line = reader.readLine()) != null) {
        Output += line + "\n";
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    return Output;
  }

  /**
  * generates hash key
  *
  * @param msg
  * @param keyString
  * @param algo
  * @return string
  */
  private static String hmacDigest(String msg, String keyString, String algo) {
    String digest = null;
    try {
      SecretKeySpec key = new SecretKeySpec((keyString).getBytes(CHAR_UTF_8), algo);
      Mac mac = Mac.getInstance(algo);
      mac.init(key);
      final byte[] bytes = mac.doFinal(msg.getBytes(CHAR_ASCII));
      StringBuffer hash = new StringBuffer();
      for (int i = 0; i < bytes.length; i++) {
        String hex = Integer.toHexString(0xFF & bytes[i]);
        if (hex.length() == 1) {
          hash.append('0');
        }
        hash.append(hex);
      }
      digest = hash.toString();
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    } catch (InvalidKeyException e) {
      e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    }
    return digest;
  }
  
  /**
  * build querystring out of params map
  *
  * @param data map of params
  * @return string
  * @throws UnsupportedEncodingException
  */
  private static String toQueryString(Map<String, String> data) {
    String queryString = "";
    try{
      StringBuffer params = new StringBuffer();
      for (Map.Entry<String, String> pair : data.entrySet()) {
        params.append(URLEncoder.encode((String) pair.getKey(), CHAR_UTF_8) + "=");
        params.append(URLEncoder.encode((String) pair.getValue(), CHAR_UTF_8) + "&");
      }
      if (params.length() > 0) {
        params.deleteCharAt(params.length() - 1);
      }
      queryString = params.toString();
    } catch(UnsupportedEncodingException e){
      e.printStackTrace();
    }
    return queryString;
  }
  
  /**
  * returns the current timestamp
  * @return current timestamp in ISO 8601 format
  */
  private static String getCurrentTimestamp(){
    final TimeZone tz = TimeZone.getTimeZone("UTC");
    final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ");
    df.setTimeZone(tz);
    final String nowAsISO = df.format(new Date());
    return nowAsISO;
  }
}

API call in Python

import urllib
from hashlib import sha256
from hmac import HMAC
from datetime import datetime

parameters = {
  'UserID': '[email protected]',
  'Version': '1.0',
  'Action': 'FeedList',
  'Format':'XML',
  'Timestamp': datetime.now().isoformat()\n}
api_key = 'b1bdb357ced10fe4e9a69840cdd4f0e9c03d77fe'
concatenated = urllib.urlencode(sorted(parameters.items()))
parameters['Signature'] = HMAC(api_key, concatenated, sha256).hexdigest()

API call in Visual Basic

Imports System
Public Module modmain
	Sub Main()
		' add your data here:	
    Dim userId As String = "" 'login name / your email
    Dim password As String = "" 'your API key/password
    Dim version As String = "1.0"
    Dim action As String = "ProductCreate"
    Dim url As String = ""
    'e.g.: "https://sellercenter-api-linio-co.sellercenter.net/"
    Dim result As String
    
    ' this is where the magic happens:
    result = generateRequest(url, userId, password, version, action)
    Console.WriteLine (result)
    End Sub

		Function generateRequest(Url As String, user As String, key as String, version As String, action As
String) As String
      Dim timeStamp as String = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss-0000")
      ' ATTENTION: parameters must be in alphabetical order
      Dim stringToHash As String = _
      "Action=" + URLEncode(action) + _
      "&Timestamp=" + URLEncode(timeStamp) + _
      "&UserID=" + URLEncode(user) + _
      "&Version=" + URLEncode(version)
      Dim hash As String = HashString(stringToHash, key)
      ' ATTENTION: parameters must be in alphabetical order
      Dim request As String = _
      "Action=" + URLEncode(action) + _
      "&Signature=" + URLEncode(hash) + _
      "&Timestamp=" + URLEncode(timeStamp) + _
      "&UserID=" + URLEncode(user) + _
      "&Version=" + URLEncode(version)
      return url + "?" + request
		End Function
    
    ' use this function instead of HttpServerUtility.UrlEncode()
    ' because we need uppercase letters
    Function URLEncode(EncodeStr As String) As String
    	Dim i As Integer
      Dim erg As String
      erg = EncodeStr
      erg = Replace(erg, "%", Chr(1))
      erg = Replace(erg, "+", Chr(2))
      For i = 0 To 255
        Select Case i
        ' *** Allowed 'regular' characters
          Case 37, 43, 45, 46, 48 To 57, 65 To 90, 95, 97 To 122, 126
          Case 1 ' *** Replace original % erg = Replace(erg, Chr(i), "%25")
          Case 2 ' *** Replace original + erg = Replace(erg, Chr(i), "%2B")
          Case 32 erg = Replace(erg, Chr(i), "+")
          Case 3 To 15 erg = Replace(erg, Chr(i), "%0" & Hex(i))
          Case Else
            erg = Replace(erg, Chr(i), "%" & Hex(i))
        End Select
      Next
      return erg
    End Function
    
    Function HashString(ByVal StringToHash As String, ByVal HachKey As String) As String
      Dim myEncoder As New System.Text.UTF8Encoding
      Dim Key() As Byte = myEncoder.GetBytes(HachKey)
      Dim Text() As Byte = myEncoder.GetBytes(StringToHash)
      Dim myHMACSHA256 As New System.Security.Cryptography.HMACSHA256(Key)
      Dim HashCode As Byte() = myHMACSHA256.ComputeHash(Text)
      Dim hash As String = Replace(BitConverter.ToString(HashCode), "-", "")
	    Return hash.ToLower
		End Function
End Module

API call in Adobe ColdFusion

<!--- this is a sample Adobe ColdFusion script for sending API request to SellerCenter --->
<!--- hashing function --->
<cffunction name="HMAC_SHA256" returntype="string" access="private" output="false">
  <cfargument name="Data" type="string" required="true" />
  <cfargument name="Key" type="string" required="true" />
  <cfargument name="Bits" type="numeric" required="false" default="256" />
  <cfset var i = 0 />
  <cfset var HexData = "" />
  <cfset var HexKey = "" />
  <cfset var KeyLen = 0 />
  <cfset var KeyI = "" />
  <cfset var KeyO = "" />
  <cfset HexData = BinaryEncode(CharsetDecode(Arguments.data, "iso-8859-1"), "hex") />
  <cfset HexKey = BinaryEncode(CharsetDecode(Arguments.key, "iso-8859-1"), "hex") />
  <cfset KeyLen = Len(HexKey)/2 />
  <cfif KeyLen gt 64>
    <cfset HexKey = Hash(CharsetEncode(BinaryDecode(HexKey, "hex"), "iso-8859-1"), "SHA-256", "iso-8859-1") />
    <cfset KeyLen = Len(HexKey)/2 />
  </cfif>
  <cfloop index="i" from="1" to="#KeyLen#">
    <cfset KeyI = KeyI & Right("0"&FormatBaseN(BitXor(InputBaseN(Mid(HexKey,2*i-
           1,2),16),InputBaseN("36",16)),16),2) />
    <cfset KeyO = KeyO & Right("0"&FormatBaseN(BitXor(InputBaseN(Mid(HexKey,2*i-
           1,2),16),InputBaseN("5c",16)),16),2) />
  </cfloop>
  <cfset KeyI = KeyI & RepeatString("36",64-KeyLen) />
  <cfset KeyO = KeyO & RepeatString("5c",64-KeyLen) />
  <cfset HexKey = Hash(CharsetEncode(BinaryDecode(KeyI&HexData, "hex"), "iso-8859-1"), "SHA-256", "iso-8859-1")
         />
  <cfset HexKey = Hash(CharsetEncode(BinaryDecode(KeyO&HexKey, "hex"), "iso-8859-1"), "SHA-256", "iso-8859-
                                                                                                  1") />
  <cfreturn Left(HexKey,arguments.Bits/4) />
</cffunction>
<!---/ hashing function --->
<!--- define --->
<cfset send_xml = false><!--- for APIs that need XML request body, like xxxxProduct APIs --->
  <cfset secret_key="562aeae4090d3a62ef171b6646cc2bdac6417473"/>
  <cfset sc_api_host="http://sellercenter-api.local/"/>
  <!---/ define --->
  <!--- request params --->
  <cfset params = structNew() />
  <cfset params["[email protected]"] = "UserID"/>
  <cfset params["GetShipmentProviders"] = "Action"/>
  <cfset params["2014-07-24T20:06:33+02:00"] = "Timestamp"/>
  <cfset params["1.0"] = "Version"/>
  <cfsavecontent variable="strXML">
    <?xml version="1.0" encoding="UTF-8" ?>
    <Request>
      <Product>
        <SellerSku>4105382173aaee4</SellerSku>
        <Price>12</Price>
      </Product>
    </Request>
  </cfsavecontent>
  <!---/ request params --->
  <!--- generate signature --->
  <cfset strtohash = "" />
  <cfloop list="#ArrayToList(StructSort(params, "text", "asc"))#" index="key" >
    <cfset strtohash = strtohash & #params[key]# & "=" & #URLEncodedFormat(key)# & "&" />
  </cfloop>
  <cfset strtohash = #RemoveChars(strtohash, len(strtohash), 1)#/>
  <cfset strtohash = #Replace(strtohash, "%2D", "-", "All")#/>
  <cfset strtohash = #Replace(strtohash, "%2E", ".", "All")#/>
  <cfset signature="#LCase(HMAC_SHA256(strtohash, secret_key))#"/>
  <!---/ generate signature --->
  <!--- issue the request --->
  <cfif send_xml>
    <cfset http_method = "post" />
  <cfelse>
    <cfset http_method = "get" />
  </cfif>
	<cfhttp method="#http_method#" url="#sc_api_host#">
	<cfloop list="#ArrayToList( StructSort(params, "text", "asc") )#" index="key" >
		<cfhttpparam type="url" name="#params[key]#" value="#key#"></cfhttpparam>
	</cfloop>
	<cfhttpparam type="xml" value="#strXML.Trim()#"></cfhttpparam>
	<cfhttpparam type="url" name="Signature" value="#signature#"></cfhttpparam>
	</cfhttp>
  <!---/ issue the request --->
  <!--- XML response --->
	<cfoutput>#cfhttp.filecontent#</cfoutput>