The Shopee Open Plaftorm mandates a specific procedure for application authorization and token management. All request to partner endpoints must include a valid signature generated from the partner key and the request path.
Signature Computation
A signature is derived using the HMAC-SHA256 algorithm. The input is a base string consisting of the partner ID, the API path, and the current Unix timestamp. The secret key is the partner key provided upon registration.
public static string ComputeHmacSha256Signature(string data, string key)
{
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
{
byte[] digest = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
return ByteArrayToHex(digest);
}
}
private static string ByteArrayToHex(byte[] bytes)
{
var hexBuilder = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
hexBuilder.AppendFormat("{0:x2}", b);
return hexBuilder.ToString();
}
Obtaining the Initial Access Token
After a shop owner completes authorization, the callback URL receives a temporary code. This code must be exchanged for a long-lived token pair. The request body includes the code, partner ID, and shop ID. The response contians an access token, refresh token, and expiration period.
public OperationResult FetchInitialToken(string authorizationCode, int partnerId, string partnerSecret, int shopId)
{
long epoch = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var payload = new
{
code = authorizationCode,
partner_id = partnerId,
shop_id = shopId
};
string jsonBody = JsonConvert.SerializeObject(payload);
string host = ConfigurationManager.AppSettings["ShopeeApiHost"];
string path = "/api/v2/auth/token/get";
string baseStr = $"{partnerId}{path}{epoch}";
string sign = ComputeHmacSha256Signature(baseStr, partnerSecret);
string fullUrl = $"{host}{path}?sign={sign}&partner_id={partnerId}×tamp={epoch}";
string response = HttpHelper.PostJson(fullUrl, jsonBody);
JObject data = JObject.Parse(response);
if (!string.IsNullOrEmpty(data["error"]?.ToString()))
return new OperationResult { Status = Status.Failure, Message = "Authorization failed" };
var credential = new ShopCredential
{
AccessToken = data["access_token"].ToString(),
RefreshToken = data["refresh_token"].ToString(),
TokenRefreshThreshold = DateTime.Now.AddSeconds(int.Parse(data["expire_in"].ToString()) - 600)
};
// Retrieve shop information for validation
string infoPath = "/api/v2/shop/get_shop_info";
string infoBase = $"{partnerId}{infoPath}{epoch}{credential.AccessToken}{shopId}";
string infoSign = ComputeHmacSha256Signature(infoBase, partnerSecret);
string infoUrl = $"{host}{infoPath}?shop_id={shopId}&partner_id={partnerId}&access_token={credential.AccessToken}&sign={infoSign}×tamp={epoch}";
string infoResp = HttpHelper.Get(infoUrl);
if (string.IsNullOrEmpty(infoResp))
return new OperationResult { Status = Status.Failure, Message = "Could not fetch shop profile" };
JObject shopInfo = JObject.Parse(infoResp);
credential.ShopName = shopInfo["shop_name"].ToString();
credential.Region = shopInfo["region"].ToString();
credential.Status = shopInfo["status"].ToString();
credential.AuthorizeTime = ConvertTimestamp(shopInfo["auth_time"].ToString());
credential.ExpireTime = ConvertTimestamp(shopInfo["expire_time"].ToString());
credential.CreatedAt = DateTime.Now;
credential.ShopId = shopId;
credential.AuthCode = authorizationCode;
// Persist credential (update or insert)
ShopCredential existing = repository.FindByShopId(shopId);
if (existing != null && existing.Id > 0)
{
credential.Id = existing.Id;
repository.Update(credential);
}
else
{
repository.Add(credential);
}
return new OperationResult { Status = Status.Success, Message = "Authorization granted" };
}
Refresh Token Rotation
Access tokens have a limited lifetime. Before every API call, verify whether the current token is still valid by checking the pre-calculated refresh threshold. If that threshold is in the past, use the stored refresh token to request a new pair. The endpoint is /api/v2/auth/access_token/get.
public void RenewAccessTokenIfNeeded(int shopId)
{
ShopCredential cred = repository.FindByShopId(shopId);
if (cred == null || cred.RefreshThreshold >= DateTime.Now)
return;
int partnerId = int.Parse(ConfigurationManager.AppSettings["ShopeePartnerId"]);
string partnerKey = ConfigurationManager.AppSettings["ShopeePartnerKey"];
long epoch = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var body = new
{
refresh_token = cred.RefreshToken,
partner_id = partnerId,
shop_id = cred.ShopId
};
string jsonBody = JsonConvert.SerializeObject(body);
string host = ConfigurationManager.AppSettings["ShopeeApiHost"];
string path = "/api/v2/auth/access_token/get";
string baseStr = $"{partnerId}{path}{epoch}";
string sign = ComputeHmacSha256Signature(baseStr, partnerKey);
string url = $"{host}{path}?sign={sign}&partner_id={partnerId}×tamp={epoch}";
string response = HttpHelper.PostJson(url, jsonBody);
JObject data = JObject.Parse(response);
cred.AccessToken = data["access_token"].ToString();
cred.RefreshToken = data["refresh_token"].ToString();
int expireSeconds = int.Parse(data["expire_in"].ToString());
cred.RefreshThreshold = DateTime.Now.AddSeconds(expireSeconds - 600);
repository.Update(cred);
}
Always call the renewal method before constructing any Shopee API request to ensure the stored access token remains usable.