Skip to content

API Key & HMAC signing

Evidence: ptrader-api-gateway/docs/API-Authentication-Mechanism.en.md, filter/AuthFilter.java, Postman collection pre-request script.

Headers

Header Required Description
x-ptrader-apikey Yes (signed & key-only) API key
true-client-ip / x-real-ip Often required via ingress Client IP; Postman sets true-client-ip

Signed requests

Required for endpoints annotated @SignAuth without validKeyOnly (orders, account, wallet, etc.).

Parameters

Parameter Required Description
timestamp Yes when checkRecvWindow=true Unix ms
recvWindow No Default 5000 ms, max 60000 ms
signature Yes HMAC-SHA256 hex (query or header)

Recv window rule

Valid when:

timestamp ≤ serverTime + 1000 and serverTime - timestamp ≤ recvWindow

Signature string (originStr)

originStr = queryStringWithoutSignature + requestBody

Rules:

  1. Query: all parameters except signature, in the order sent, joined with & as key=value.
  2. Body:
  3. application/json → compact JSON string (gateway parses then re-serializes via Jackson JsonNode.toString()).
  4. application/x-www-form-urlencoded → form fields not already in query (exclude signature).
  5. Concatenate query and body with no extra separator.
signature = Hex(HMAC-SHA256(apiSecret, originStr))

Worked example (LIMIT order, query-only body empty)

Illustrative credentials (from api-gateway auth doc — not production keys):

  • API Key: vmPUZE6mv9SD5VNHk4HlWFsOr6aKE2zvsw0MuIgwCIPy6utIco14y7Ju91duEh8A
  • API Secret: NhqPtmdSJYdKjVHjA7PZj4Mge3R5YNiP1e3UZjInClVN65XAbvqqM6A7H5fATj0j
API_KEY="vmPUZE6mv9SD5VNHk4HlWFsOr6aKE2zvsw0MuIgwCIPy6utIco14y7Ju91duEh8A"
API_SECRET="NhqPtmdSJYdKjVHjA7PZj4Mge3R5YNiP1e3UZjInClVN65XAbvqqM6A7H5fATj0j"
BASE_URL="https://api-gateway-scb.dev.pcrypto.com"
TIMESTAMP=$(python3 -c 'import time; print(int(time.time()*1000))')

QUERY="symbol=BTCUSDT&side=BUY&type=LIMIT&timeInForce=GTC&quantity=0.001&price=50000&timestamp=${TIMESTAMP}&recvWindow=5000"
SIGNATURE=$(printf '%s' "${QUERY}" | openssl dgst -sha256 -hmac "${API_SECRET}" | awk '{print $2}')

curl -sS -X POST "${BASE_URL}/openapi/v1/order?${QUERY}&signature=${SIGNATURE}" \
  -H "x-ptrader-apikey: ${API_KEY}" \
  -H "true-client-ip: 1.2.3.4" \
  -H "Content-Type: application/x-www-form-urlencoded"

For JSON bodies, append the compact JSON string to originStr after the query string (Postman script: JSON.stringify(JSON.parse(raw))).

Key-only endpoints (listenKey)

POST|PUT|DELETE /openapi/v1/userDataStream use @SignAuth(validKeyOnly = true, accountOnly = true).

Evidence: UserDataApiController; AuthFilter branch signAuth.validKeyOnly() validates the key via broker-security without building/checking a signature.

Send x-ptrader-apikey only (Postman X-Skip-Sign is a client-side hint and is not interpreted by the gateway).

Org resolution

BrokerInterceptor resolves x-org-id from Host → broker apiDomain. Failure → INVALID_PARAMETER (host).