> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qfex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 身份认证

连接 `trade.qfex.com` 后，必须在 **1 分钟内**完成认证，否则服务器会关闭连接。认证方式为 HMAC-SHA256 签名或有效 JWT 令牌。

## 工作方式

### HMAC

1. 连接 `wss://trade.qfex.com?api_key=YOUR_PUBLIC_KEY`。
2. 生成密码学安全随机 nonce（十六进制编码，最长 100 个字符），并获取当前 Unix 时间戳。
3. 构造字符串 `${nonce}:${unix_ts}`，使用私密密钥计算 HMAC-SHA256，再将结果编码为十六进制签名。
4. 发送包含下述 `hmac` 对象的认证载荷。nonce 在 15 分钟窗口内必须唯一。

```json theme={null}
{
  "type": "auth",
  "params": {
    "hmac": {
      "public_key": "qfex_pub_xxxxx",
      "nonce": "c0ffee...",
      "unix_ts": 1760545414,
      "signature": "5f2e..."
    },
    "account_id": "11111111-1111-1111-1111-111111111111"
  }
}
```

### JWT

对于 JWT 会话，包括通过 QFEX OAuth 获取的令牌，连接时将 JWT 作为查询参数：

```text theme={null}
wss://trade.qfex.com/?jwt=URL_ENCODED_JWT
```

随后使用同一个 JWT 认证：

```json theme={null}
{
  "type": "auth",
  "params": {
    "jwt": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImNiYzVmZWNmLTlhODItNGFlNy04NDFkLTBkMTdjMjUzMWM3OCIsInR5cCI6IkpXVCJ9...",
    "account_id": "11111111-1111-1111-1111-111111111111"
  }
}
```

要使已认证交易 WebSocket 会话使用子账户，在 `hmac` 或 `jwt` 旁添加可选字段 `account_id`。省略时，连接使用主账户。

## Builder 归因

要将交易手续费归因给开发者，在同一认证载荷中添加可选的 `builder_code`。该代码为开发者在开发者设置或通过 [Builder Codes API](https://docs.qfex.com/cn/api-reference/builder-codes.md)创建的 UUID。

```json theme={null}
{
  "type": "auth",
  "params": {
    "jwt": "USER_JWT",
    "builder_code": "11111111-1111-4111-8111-111111111111"
  }
}
```

代码在连接的整个生命周期内生效。该连接提交的普通订单、触发单和 TWAP 订单自动继承代码，无需在每个订单中重复添加。

浏览器 WebSocket 客户端不能设置自定义握手请求头，因此应通过 `params.builder_code` 传递代码。完整 OAuth 至交易流程见[开发者集成指南](https://docs.qfex.com/cn/api-reference/builder-integration.md)。

## 示例代码

```python python theme={null}
# Python (websocket-client)
# pip install websocket-client
import hashlib
import hmac
import json
import secrets
import time

import websocket

PUB_KEY = "qfex_pub_xxxxx"
SECRET_KEY = "qfex_secret_yyyyyy"


def build_auth_message():
    nonce = secrets.token_hex(16)
    unix_ts = int(time.time())
    payload = f"{nonce}:{unix_ts}".encode()
    signature = hmac.new(SECRET_KEY.encode(), payload, hashlib.sha256).hexdigest()
    return {
        "type": "auth",
        "params": {
            "hmac": {
                "public_key": PUB_KEY,
                "nonce": nonce,
                "unix_ts": unix_ts,
                "signature": signature,
            }
        },
    }


def on_open(ws):
    ws.send(json.dumps(build_auth_message()))


def on_message(_, message):
    print("Message:", message)


def on_error(_, error):
    print("Error:", error)


def on_close(_, code, reason):
    print("Closed:", code, reason)


ws = websocket.WebSocketApp(
    "wss://trade.qfex.com?api_key=" + PUB_KEY,
    on_open=on_open,
    on_message=on_message,
    on_error=on_error,
    on_close=on_close,
)
ws.run_forever()
```

```javascript node theme={null}
// Node.js (ws)
// npm i ws
import crypto from "crypto";
import WebSocket from "ws";

const pubKey = "qfex_pub_xxxxx";
const secretKey = "qfex_secret_yyyyyy";

function buildAuthMessage() {
  const nonce = crypto.randomBytes(16).toString("hex");
  const unixTs = Math.floor(Date.now() / 1000);
  const payload = `${nonce}:${unixTs}`;
  const signature = crypto
    .createHmac("sha256", secretKey)
    .update(payload)
    .digest("hex");

  return {
    type: "auth",
    params: {
      hmac: {
        public_key: pubKey,
        nonce,
        unix_ts: unixTs,
        signature,
      },
    },
  };
}

const ws = new WebSocket(`wss://trade.qfex.com?api_key=${pubKey}`);

ws.on("open", () => {
  ws.send(JSON.stringify(buildAuthMessage()));
});

ws.on("message", (msg) => {
  console.log("Message:", msg.toString());
});

ws.on("error", (err) => console.error("WS error:", err));
ws.on("close", (code, reason) => {
  console.log("Closed:", code, reason?.toString() ?? "");
});
```

```go go theme={null}
// Go (gorilla/websocket)
// go get github.com/gorilla/websocket
package main

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"log"
	"os"
	"os/signal"
	"time"

	"github.com/gorilla/websocket"
)

func main() {
	pubKey := "qfex_pub_xxxxx"
	secretKey := []byte("qfex_secret_yyyyyy")

	c, _, err := websocket.DefaultDialer.Dial("wss://trade.qfex.com?api_key="+pubKey, nil)
	if err != nil {
		log.Fatal("dial:", err)
	}
	defer c.Close()

	nonceBytes := make([]byte, 16)
	if _, err := rand.Read(nonceBytes); err != nil {
		log.Fatal("nonce:", err)
	}
	nonce := hex.EncodeToString(nonceBytes)
	unixTs := time.Now().Unix()
	message := fmt.Sprintf("%s:%d", nonce, unixTs)

	h := hmac.New(sha256.New, secretKey)
	h.Write([]byte(message))
	signature := hex.EncodeToString(h.Sum(nil))

	auth := map[string]any{
		"type": "auth",
		"params": map[string]any{
			"hmac": map[string]any{
				"public_key": pubKey,
				"nonce":      nonce,
				"unix_ts":    unixTs,
				"signature":  signature,
			},
		},
	}

	if err := c.WriteJSON(auth); err != nil {
		log.Fatal("write auth:", err)
	}

	go func() {
		for {
			_, message, err := c.ReadMessage()
			if err != nil {
				log.Println("read:", err)
				return
			}
			fmt.Printf("Message: %s\n", message)
		}
	}()

	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt)
	<-interrupt
}
```

```java java theme={null}
// Java (OkHttp WebSocket)
// Gradle: implementation("com.squareup.okhttp3:okhttp:4.12.0")
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.concurrent.TimeUnit;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import okhttp3.*;

public class AuthWs {
  private static final String PUB_KEY = "qfex_pub_xxxxx";
  private static final String SECRET_KEY = "qfex_secret_yyyyyy";

  public static void main(String[] args) {
    OkHttpClient client = new OkHttpClient.Builder()
        .pingInterval(20, TimeUnit.SECONDS)
        .build();

    Request request = new Request.Builder()
        .url("wss://trade.qfex.com?api_key=" + PUB_KEY)
        .build();

    WebSocketListener listener = new WebSocketListener() {
      @Override public void onOpen(WebSocket ws, Response response) {
        ws.send(buildAuthPayload());
      }

      @Override public void onMessage(WebSocket ws, String text) {
        System.out.println("Message: " + text);
      }

      @Override public void onFailure(WebSocket ws, Throwable t, Response r) {
        System.err.println("WS error: " + t.getMessage());
      }
    };

    client.newWebSocket(request, listener);

    try {
      Thread.sleep(Long.MAX_VALUE);
    } catch (InterruptedException ignored) {
      Thread.currentThread().interrupt();
    }
  }

  private static String buildAuthPayload() {
    SecureRandom random = new SecureRandom();
    byte[] nonceBytes = new byte[16];
    random.nextBytes(nonceBytes);
    String nonce = bytesToHex(nonceBytes);

    long unixTs = System.currentTimeMillis() / 1000;
    String data = nonce + ":" + unixTs;

    String signature;
    try {
      Mac mac = Mac.getInstance("HmacSHA256");
      SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
      mac.init(keySpec);
      signature = bytesToHex(mac.doFinal(data.getBytes(StandardCharsets.UTF_8)));
    } catch (Exception e) {
      throw new IllegalStateException("Unable to sign payload", e);
    }

    return String.format(
        "{\"type\":\"auth\",\"params\":{\"hmac\":{\"public_key\":\"%s\",\"nonce\":\"%s\",\"unix_ts\":%d,\"signature\":\"%s\"}}}",
        PUB_KEY, nonce, unixTs, signature);
  }

  private static String bytesToHex(byte[] bytes) {
    StringBuilder sb = new StringBuilder(bytes.length * 2);
    for (byte b : bytes) {
      sb.append(String.format("%02x", b));
    }
    return sb.toString();
  }
}
```

## 响应示例

```json theme={null}
{ "type": "auth", "result": "success" }
```

> [!NOTE]
>
> 客户端成功认证后，服务器通过定期发送 [WebSocket ping 帧](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#pings-and-pongs-the-heartbeat-of-websockets)形式的心跳消息保持连接。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.