Auth & Signature
Awice Logistics Open Platform uses industry-standard HMAC-SHA256 signing + IP whitelist dual authentication to protect AppKeys at the protocol level. This page details the signing algorithm and common pitfalls.
Signature Formula
Signature = HMAC-SHA256(
AppSecret,
METHOD + "\n" + PATH + "\n" + Timestamp + "\n" + Nonce + "\n" + MD5(BODY)
)Concatenate the 5 fields with a newline character (\n i.e. byte 0x0A) into a message string. Then use AppSecret as the key and run HMAC-SHA256 over the message string. The output is a 64-char lowercase hex string used as the signature.
Request Header Spec
Every OpenAPI request must carry the following 4 HTTP headers:
| Header | Required | Description |
|---|---|---|
| X-Awice-AppKey | Yes | The AppKey created in the console |
| X-Awice-Timestamp | Yes | Unix timestamp in seconds, tolerance ±300s |
| X-Awice-Nonce | Yes | 8-32 byte random string, unique within 10 minutes |
| X-Awice-Signature | Yes | HMAC-SHA256 signature (64-char lowercase hex) |
Field Computation Details
- METHOD:Uppercase HTTP method (
POST/GET), without the HTTP version. - PATH:Request path, without the query string, without the host. For example
/openapi/v1/freight/fcl/search. Note: the request URL ishttps://www.5688.cn/api+ PATH, but only PATH is used in the signature. - Timestamp:Unix timestamp in seconds. The server tolerates ±300s of client clock drift. Fetch it fresh per request.
- Nonce:A unique random string per request. The server caches all nonces seen in the last 10 minutes. Reuse returns 2004.
- MD5(BODY):32-char lowercase hex MD5 of the request body.
- POST/PUT: MD5 the raw JSON string (watch whitespace, ensure stable field order)
- GET/DELETE: BODY is an empty string,
md5("") = d41d8cd98f00b204e9800998ecf8427e
Common Causes of Signature Errors
If you receive 2002 signature failure, 95% of the time it's one of these:
- Clock not synced:Cloud server / local NTP is off, time drift > 5 minutes. You'd actually get 2003, not 2002. Check system time with
date. - Path includes query string:Only PATH may be used in the signature. Wrong:
/openapi/v1/x?a=1Right:/openapi/v1/x. - JSON serialization is inconsistent:Client uses
json.dumps()with default whitespace vs without — the MD5 differs entirely. Usejson.dumps(..., separators=(',', ':'))for compact format. The server MD5s the raw body you send (it does not re-serialize). - AppSecret contains stray whitespace:Pasted from the console with a trailing newline or space;
trim()it in your code. - Wrong METHOD case:Must be
POST(uppercase), notpost.
Multi-language Signature Helpers
PHP
<?php
function buildSignature(string $method, string $path, string $body, string $appSecret): array {
$ts = (string)time();
$nonce = bin2hex(random_bytes(8));
$msg = strtoupper($method) . "\n" . $path . "\n" . $ts . "\n" . $nonce . "\n" . md5($body);
$sign = hash_hmac('sha256', $msg, $appSecret);
return [
'timestamp' => $ts,
'nonce' => $nonce,
'signature' => $sign,
];
}
// Usage:
$body = json_encode(['pol' => 'CNSHA', 'pod' => 'USLSA']);
$hdrs = buildSignature('POST', '/openapi/v1/freight/fcl/search', $body, $appSecret);Python
import hmac, hashlib, time, secrets
def build_signature(method: str, path: str, body: str, app_secret: str) -> dict:
ts = str(int(time.time()))
nonce = secrets.token_hex(8)
body_md5 = hashlib.md5(body.encode()).hexdigest()
msg = f"{method.upper()}\n{path}\n{ts}\n{nonce}\n{body_md5}".encode()
sig = hmac.new(app_secret.encode(), msg, hashlib.sha256).hexdigest()
return {'timestamp': ts, 'nonce': nonce, 'signature': sig}
# Usage:
import json
body = json.dumps({'pol': 'CNSHA', 'pod': 'USLSA'})
hdrs = build_signature('POST', '/openapi/v1/freight/fcl/search', body, app_secret)Node.js
const crypto = require('crypto');
function buildSignature(method, path, body, appSecret) {
const ts = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(8).toString('hex');
const bodyMd5 = crypto.createHash('md5').update(body).digest('hex');
const msg = `${method.toUpperCase()}\n${path}\n${ts}\n${nonce}\n${bodyMd5}`;
const sig = crypto.createHmac('sha256', appSecret).update(msg).digest('hex');
return { timestamp: ts, nonce, signature: sig };
}
// Usage:
const body = JSON.stringify({ pol: 'CNSHA', pod: 'USLSA' });
const hdrs = buildSignature('POST', '/openapi/v1/freight/fcl/search', body, appSecret);Go
package awice
import (
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
"time"
)
type SignHeaders struct {
Timestamp string
Nonce string
Signature string
}
func BuildSignature(method, path, body, appSecret string) (*SignHeaders, error) {
ts := fmt.Sprintf("%d", time.Now().Unix())
nonceBytes := make([]byte, 8)
if _, err := rand.Read(nonceBytes); err != nil {
return nil, err
}
nonce := hex.EncodeToString(nonceBytes)
bodyMd5 := md5.Sum([]byte(body))
bodyMd5Hex := hex.EncodeToString(bodyMd5[:])
msg := strings.ToUpper(method) + "\n" + path + "\n" + ts + "\n" + nonce + "\n" + bodyMd5Hex
h := hmac.New(sha256.New, []byte(appSecret))
h.Write([]byte(msg))
sig := hex.EncodeToString(h.Sum(nil))
return &SignHeaders{Timestamp: ts, Nonce: nonce, Signature: sig}, nil
}Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.HexFormat;
import java.util.Map;
public class AwiceSigner {
public static Map<String, String> buildSignature(
String method, String path, String body, String appSecret) throws Exception {
String ts = String.valueOf(System.currentTimeMillis() / 1000);
byte[] nonceBytes = new byte[8];
new SecureRandom().nextBytes(nonceBytes);
String nonce = HexFormat.of().formatHex(nonceBytes);
String bodyMd5 = HexFormat.of().formatHex(
MessageDigest.getInstance("MD5").digest(body.getBytes(StandardCharsets.UTF_8)));
String msg = method.toUpperCase() + "\n" + path + "\n" + ts + "\n" + nonce + "\n" + bodyMd5;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String signature = HexFormat.of().formatHex(mac.doFinal(msg.getBytes(StandardCharsets.UTF_8)));
return Map.of("timestamp", ts, "nonce", nonce, "signature", signature);
}
// Usage:
// String body = "{ \"pol\":\"CNSHA\",\"pod\":\"USLSA\" }";
// Map<String,String> hdrs = buildSignature("POST", "/openapi/v1/freight/fcl/search", body, appSecret);
}