API Authentication & Signature
All OpenAPI module endpoints require an MD5 token. Every request must include three HTTP headers.
Required Headers
| Header | Description |
|---|---|
x-auth-name | API client name issued by WMG |
x-auth-seed | 13-digit millisecond Unix timestamp (e.g., 1746700000000) |
x-auth-token | MD5 authentication token (see formula below) |
Seed
The platform clock runs on Singapore time (UTC+08:00). The x-auth-seed window is checked against that clock.
Rules
- 13-digit numeric Unix timestamp in milliseconds — not seconds, and not a formatted date string.
- Must be within ±10 minutes of platform time, otherwise the request is rejected with
x-auth-seed: TIMEOUT. - Generate it immediately before sending the request, not once at script startup.
A Unix timestamp is an absolute instant, so the correct value is the same number whether your machine is set to Singapore time or to any other zone — provided your system clock is accurate and your client converts it properly. Each method listed below yields the value that matches Singapore platform time.
x-auth-seed | Singapore time (UTC+08:00) |
|---|---|
1746700000000 | 2025-05-08 18:26:40 +08:00 |
1753753069000 | 2025-07-29 09:37:49 +08:00 |
Generating the seed
| Language | Use |
|---|---|
| Bash | SEED=$(date +%s%3N) |
| PowerShell | $SEED = [string]([DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()) |
| Python | seed = str(int(time.time() * 1000)) |
| Node.js | const seed = String(Date.now()); |
| PHP | $SEED = (string)(time() * 1000); |
PowerShell: do not use Get-Date -UFormat %s
It returns a 10-digit seconds value, which is rejected with x-auth-seed: LENGTH MUST BE 13 DIGITS. On Windows PowerShell 5.1 it is additionally derived from local time — on a machine set to Singapore time it runs 28800 seconds (8 hours) ahead of the real Unix timestamp.
# Windows PowerShell 5.1, machine set to Singapore Standard Time
Get-Date -UFormat %s # 1785149525 <- seconds, and 8 hours ahead
[DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() # 1785120724000 <- correctPowerShell 7 returns the correct instant for both, so the clock fault only surfaces once the same script runs under 5.1 — but the digit count is wrong in either version. Always use [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds().
Token Formula
x-auth-token = md5( strtolower(api_name) + api_key + seed )The result is a lowercase hex string (32 characters). No request parameters are included in the token.
Step-by-Step
- Lowercase the API name — convert
x-auth-nameto lowercase. - Concatenate the lowercased name, your
api_key, and theseedstring — no separators. - MD5 hash the concatenated UTF-8 string.
- Hex-encode the raw binary hash to produce a 32-character lowercase string.
- Place the result in the
x-auth-tokenheader.
Note: The seed must be a string when concatenated — do not convert to an integer first. The MD5 input is the raw UTF-8 bytes of the concatenated string.
Reference Implementations
Bash
API_NAME="your_api_name"
API_KEY="your_api_key"
SEED=$(date +%s%3N)
TOKEN=$(printf '%s' "$(echo -n "$API_NAME" | tr '[:upper:]' '[:lower:]')${API_KEY}${SEED}" | md5sum | cut -d' ' -f1)
curl -G "https://api.test.wmgdelivery.com/v1/openapi/station/get-stations" \
-H "x-auth-name: $API_NAME" \
-H "x-auth-seed: $SEED" \
-H "x-auth-token: $TOKEN"Windows PowerShell
$API_NAME = "your_api_name"
$API_KEY = "your_api_key"
$SEED = [string]([DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds())
$raw = [System.Text.Encoding]::UTF8.GetBytes($API_NAME.ToLower() + $API_KEY + $SEED)
$md5 = [System.Security.Cryptography.MD5]::Create().ComputeHash($raw)
$TOKEN = -join ($md5 | ForEach-Object { $_.ToString("x2") })
$headers = @{
"x-auth-name" = $API_NAME
"x-auth-seed" = $SEED
"x-auth-token" = $TOKEN
}
$response = Invoke-RestMethod -Uri "https://api.test.wmgdelivery.com/v1/openapi/station/get-stations" -Method Get -Headers $headers
$response | ConvertTo-Json -Depth 10Python
import time, hashlib
import requests
API_NAME = 'your_api_name'
API_KEY = 'your_api_key'
def build_token(api_name: str, api_key: str, seed: str) -> str:
"""seed must be the millisecond timestamp as a string — do not convert to int."""
raw = api_name.lower() + api_key + seed
return hashlib.md5(raw.encode()).hexdigest()
seed = str(int(time.time() * 1000))
token = build_token(API_NAME, API_KEY, seed)
resp = requests.get(
'https://api.test.wmgdelivery.com/v1/openapi/station/get-stations',
headers={
'x-auth-name': API_NAME,
'x-auth-seed': seed,
'x-auth-token': token,
},
)
print(resp.json())Node.js / TypeScript
Uses only the built-in node:crypto module and the global fetch (Node.js 18+).
import crypto from 'node:crypto';
const API_NAME = 'your_api_name';
const API_KEY = 'your_api_key';
function buildToken(apiName: string, apiKey: string, seed: string): string {
// seed must remain a string; concatenation runs over raw UTF-8 bytes
return crypto.createHash('md5')
.update(apiName.toLowerCase() + apiKey + seed)
.digest('hex');
}
const seed = String(Date.now());
const token = buildToken(API_NAME, API_KEY, seed);
const resp = await fetch('https://api.test.wmgdelivery.com/v1/openapi/station/get-stations', {
headers: {
'x-auth-name': API_NAME,
'x-auth-seed': seed,
'x-auth-token': token,
},
});
console.log(JSON.stringify(await resp.json(), null, 2));PHP
<?php
function build_token(string $api_name, string $api_key, string $seed): string
{
return md5(strtolower($api_name) . $api_key . $seed);
}
$API_NAME = 'your_api_name';
$API_KEY = 'your_api_key';
$SEED = (string)(time() * 1000); // 13-digit millisecond timestamp
$TOKEN = build_token($API_NAME, $API_KEY, $SEED);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.test.wmgdelivery.com/v1/openapi/station/get-stations');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'x-auth-name: ' . $API_NAME,
'x-auth-seed: ' . $SEED,
'x-auth-token: ' . $TOKEN,
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
curl_close($ch);
echo json_encode(json_decode($response, true), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
?>Usage Example
Input Parameters
API credentials:
api_name = "Demo_Client"
api_key = "abc"Seed (millisecond timestamp):
seed = "1746700000000"Execution
$token = build_token('Demo_Client', 'abc', '1746700000000');Result
Concatenated input string:
demo_clientabc1746700000000MD5 hash result:
6e7190914323a4b7e2f94ba798a3f015This hash is reproducible — feeding the same three inputs (Demo_Client, abc, 1746700000000) to any MD5 implementation always yields exactly this value. Use it as a fixture to validate your own implementation before going live.
Step-by-Step Breakdown of Example
1. Lowercase the API Name
"Demo_Client" → "demo_client"2. Concatenate
"demo_client" + "abc" + "1746700000000"
= "demo_clientabc1746700000000"3. MD5 Hash
Compute MD5 of the UTF-8 bytes of the concatenated string (binary output), then hex-encode:
md5("demo_clientabc1746700000000") → 6e7190914323a4b7e2f94ba798a3f015Integration into API Requests
Once generated, include the token in your API requests:
- Set your API name in the
x-auth-nameHTTP header - Set the seed in the
x-auth-seedHTTP header - Set the computed token in the
x-auth-tokenHTTP header
Example:
x-auth-name: Demo_Client
x-auth-seed: 1746700000000
x-auth-token: 6e7190914323a4b7e2f94ba798a3f015Token Verification Tips
| Issue | Likely Cause |
|---|---|
x-auth-token: INVALID | API name not lowercased before concatenation; wrong key or seed used |
x-auth-seed: TIMEOUT | Seed generated at script startup instead of just before the request; client clock drift; or Get-Date -UFormat %s on Windows PowerShell 5.1 (see Seed) |
x-auth-seed: LENGTH MUST BE 13 DIGITS | Seconds sent instead of milliseconds (10 digits) — multiply by 1000, see Seed |
Error Responses
| Error Message | Cause |
|---|---|
x-auth-seed: FORMAT ERROR | Seed is not numeric |
x-auth-seed: LENGTH MUST BE 13 DIGITS | Seed is not exactly 13 digits |
x-auth-seed: TIMEOUT | Seed differs from platform time by more than 10 minutes |
x-auth-name: INVALID | api_name not found or account disabled |
x-auth-token: INVALID | Token mismatch |
Standard Response Envelope
All endpoints return JSON with the following envelope:
{
"code": 0,
"message": "success",
"data": {}
}| Field | Type | Description |
|---|---|---|
code | integer | 0 = success, 1 = error |
message | string | Human-readable message |
data | object/array | Response payload; [] on error |
Unexpected server-side failures are also returned as code: 1 with the message something error.