This file describes public and authenticated transports, HMAC credentials, response decoding, and session correlation. Documentation was re-read on 2026-10-03; dated observations describe protocol behavior without account data.
TL;DR
- Trading and open-order reads use the authenticated trade WebSocket; REST provides reference data, market data, positions, history, equity, and leverage.
- HMAC signs
nonce:unix_ts, with seconds rather than milliseconds. The method, path, and body are outside the signature. - A successful WebSocket upgrade still requires an auth frame and channel subscriptions.
- There is no general request ID. Serialize reads of the same response kind; match placements by exact client ID and cancellations by exact order ID.
- Failed reads remain errors. A timeout after sending a write is an unknown outcome.
1. Hosts and API scope
| Surface | Main host | Purpose |
|---|---|---|
| REST | https://api.qfex.com | Public data and authenticated user reads |
| Trade WebSocket | wss://trade.qfex.com | Order entry, cancellation, order queries, account events |
| Market-data WebSocket | wss://mds.qfex.com | Public quotes and live bands |
| Funding service | https://banker.qfex.com | Funding API surface, outside the toolkit's trading implementation |
The corresponding UAT hosts use qfex.io. Public reference-data and handshake checks succeeded on 2026-09-30; account provisioning and authenticated UAT behavior remain unverified. Select a deployment explicitly and keep the REST and WebSocket account selection consistent.
There is no JavaScript SDK in the implementation evaluated here. The official Go CLI is a GPL-3.0 behavioral reference; this toolkit does not copy its code. Additional APIs advertised in newer documentation are not automatically implemented by the toolkit.
2. HMAC and account selection
The documented REST headers are x-qfex-public-key, x-qfex-hmac-signature, x-qfex-nonce, and x-qfex-timestamp. An optional x-qfex-requested-account-id selects an account. Signature construction is:
signature = hex(HMAC-SHA256(secret, nonce + ":" + unixTimestampSeconds))
The nonce is cryptographically random hexadecimal, at most 100 characters, unique in a 15-minute window. The accepted timestamp tolerance was approximately five minutes in the documentation examined. Build fresh credentials for each request and preserve the canonical account ID returned by the server. Changing UUID letter case caused a server error in an observation on 2026-10-01; UUID semantic equality does not guarantee API equivalence.
The REST timestamp header is a string; the auth frame's unix_ts is a JSON integer. The auth frame contains type: 'auth', with params.hmac holding public_key, nonce, unix_ts, and signature; optional params.account_id selects a subaccount. JWT authentication is documented but is not the implemented HMAC workflow.
Security inference: the signature authenticates credentials and freshness, not a particular request body. Protect the secret according to the permissions granted to the key. View-only credentials plus a local allowlist for read messages support read-only runs. Unknown frame types must not pass that allowlist.
API keys have account scope and separate execute, order-read, position-read, balance-read, and funding permissions. Documentation says funding permissions require all-account scope. A response listing one account does not independently prove key scope when the user owns only one account.
3. WebSocket setup and correlation
Query authentication with api_key and HMAC upgrade headers both accepted the handshake on 2026-10-01. The core createTradeSession follows published query authentication. Header authentication is isolated in experimental createHeaderAuthTradeSession, because the observed upgrade does not establish its complete authenticated scope. Authenticate within one minute after connecting. Headers do not replace the auth frame. Observed success was {authenticated: true}; documentation also shows {type: 'auth', result: 'success'}. Accept both deliberately.
Subscribe one channel per frame using params.channels, and await its acknowledgement. The implemented workflow subscribes to order_responses and fills before submitting orders. Positions, balances, and stop-order channels are documented and were accepted on 2026-10-01. An empty position snapshot may not be pushed on subscription.
Placement events match the exact client_order_id. Cancellation events match order_id. Read replies use response keys such as all_orders_response, user_leverage_response, and available_leverage_levels_response. Two simultaneous queries of the same kind cannot be distinguished. If a read times out, replace the connection before issuing that read kind again, so a late reply cannot satisfy a new request.
On reconnect, authenticate and subscribe again. Do not queue placement requests across disconnects. A socket drop after sending is handled by outcome resolution in orders.md §6.
4. REST errors, cache evidence, and clock evidence
Observed validation failures used RFC 7807 JSON; authentication and missing routes also used plain text. Do not require every error to be application/problem+json. Parse body and HTTP status separately.
The read client uses explicit pacing, timeouts, and retry policy. Retry transport errors and selected server errors for reads only. Ordinary 4xx answers are final for that attempt. HTTP 429 and Retry-After pause the client; live signed-REST throttling was not measured. A CDN HTML refusal is not sufficient evidence that a key was revoked.
Signed responses should be rejected when cache headers prove they came from a cached object. A cache miss does not prove the backend snapshot is current. Use manual redirect handling so custom credential headers cannot be forwarded to an unexpected host.
The HTTP Date header has one-second precision. For a request sent at sentAt and received at receivedAt, use a possible skew interval [Date - receivedAt, Date + 1000ms - sentAt], rather than asserting an exact server-clock offset.
5. Frame variants
Observed trade errors used {err: {error_code, message, incoming_message}}. The echo may be an object or JSON text. Documented flat Err and error alternatives are also accepted. Market-data errors use a different flat form. An error with no usable echo cannot be assigned to one placement merely because it arrived next.
Known codes include RateLimited, InvalidJSONFormat, AlreadyAuthenticated, InvalidParameter, PermissionDenied, ServerError, InvalidOrder, InvalidOrderId, KycRequired, and TncRequired. Preserve unknown codes and raw diagnostic context after redaction.
Pitfalls
| What breaks | Why | Correct approach |
|---|---|---|
| Upgrade succeeds, but trading fails | Connection authentication is incomplete | Send auth and await subscriptions |
| A query gets another query's response | No request ID for that response kind | One outstanding read per kind |
| Credentials leak through redirects or logs | Custom headers and query URLs contain credentials | Manual redirects and redaction |
| A canonical account works but an altered ID fails | Server account lookup is case-sensitive in observed behavior | Preserve server spelling |
| A stale read is accepted after an HTTP cache miss | CDN and indexer freshness are different | Validate snapshot consistency separately |
Open questions / not verified
- Authenticated subaccount selection and event scope across several subaccounts.
- JWT, builder attribution, and onboarding flows beyond the implemented HMAC client.
- REST and handshake ceilings, signed CDN caching behavior under failures, and heartbeat-channel semantics.
- New endpoints may differ from earlier documentation; public-account routes returned 404 in probes on 2026-09-30.
Sources
Official authentication: Authenticate; API-key permissions: Api keys; REST schema: QFEX REST schema; trade schema: QFEX trade schema; errors: Errors. Documentation checked 2026-10-03. Response variants and canonical-ID behavior were observed 2026-10-01; transport safeguards are implementation choices.
© markpaper authors. Licensed under CC BY 4.0: when publishing or adapting this material, credit “markpaper — QFEX knowledge base” and link to the original and the license.