所有商户 API(前缀 /v1/acquiring)必须携带以下 Header:
| Header | 示例 | 说明 |
|---|---|---|
| Date | Tue, 21 Jan 2025 12:00:00 GMT | GMT 格式服务器时间 |
| Authorization | Signature keyId="xxx"... | HMAC 签名认证头 |
签名字符串(signing_string)格式如下:
{keyId}
{METHOD} {path}
date: {GMT_time}示例:
merchant-001
POST /v1/acquiring/order
date: Tue, 21 Jan 2025 12:00:00 GMT使用商户的 secret_key 对 signing_string 进行 HMAC-SHA256 计算,并进行 Base64 编码:
signature = base64.b64encode(
hmac.new(secret_key, signing_string.encode(), hashlib.sha256).digest()
).decode()算法定义:
signature = Base64( HMAC_SHA256(secret_key, signing_string) )完整的 Authorization Header:
Authorization: Signature keyId ="{keyId}", algorithm = "hmac-sha256", headers = "@request-target date",signature = "{signature}"示例:
Authorization: Signature keyId = "merchant-001",
algorithm = "hmac-sha256",
headers= "@request-target date",
signature = "F0k29e...="class InfiniClient:
def __init__(self, key_id, secret_key, base_url="https://openapi.infini.money"):
self.key_id = key_id
self.secret_key = secret_key.encode() if isinstance(secret_key, str) else secret_key
self.base_url = base_url
def _sign_request(self, method, path):
gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')
signing_string = f"{self.key_id}\n{method} {path}\ndate: {gmt_time}\n"
signature = base64.b64encode(
hmac.new(self.secret_key, signing_string.encode(), hashlib.sha256).digest()
).decode()
return {
"Date": gmt_time,
"Authorization": f'Signature keyId="{self.key_id}",algorithm="hmac-sha256",'
f'headers="@request-target date",signature="{signature}"'
}
def request(self, method, path, json=None):
headers = self._sign_request(method, path)
if json is not None:
headers["Content-Type"] = "application/json"
response = requests.request(method, f"{self.base_url}{path}", json=json, headers=headers)
response.raise_for_status()
return response.json()const crypto = require("crypto");
const axios = require("axios");
class InfiniClient {
constructor(keyId, secretKey, baseUrl = "https://openapi.infini.money") {
this.keyId = keyId;
this.secretKey = secretKey;
this.baseUrl = baseUrl;
}
_signRequest(method, path) {
const gmtTime = new Date().toUTCString();
const signingString =
`${this.keyId}\n` +
`${method.toUpperCase()} ${path}\n` +
`date: ${gmtTime}\n`;
const signature = crypto
.createHmac("sha256", this.secretKey)
.update(signingString)
.digest("base64");
return {
"Date": gmtTime,
"Authorization":
`Signature keyId="${this.keyId}",algorithm="hmac-sha256",headers="@request-target date",signature="${signature}"`
};
}
async request(method, path, json = null) {
const headers = this._signRequest(method, path);
if (json !== null) {
headers["Content-Type"] = "application/json";
}
const resp = await axios({
method,
url: `${this.baseUrl}${path}`,
data: json,
headers,
});
return resp.data;
}
}
module.exports = InfiniClient;package infiniclient
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
)
type InfiniClient struct {
KeyID string
SecretKey string
BaseURL string
Client *http.Client
}
func NewInfiniClient(keyID, secretKey string, baseURL string) *InfiniClient {
if baseURL == "" {
baseURL = "https://openapi.infini.money"
}
return &InfiniClient{
KeyID: keyID,
SecretKey: secretKey,
BaseURL: baseURL,
Client: &http.Client{Timeout: 15 * time.Second},
}
}
func (c *InfiniClient) signRequest(method, path string) (map[string]string, error) {
gmtTime := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")
signingString := fmt.Sprintf(
"%s\n%s %s\ndate: %s\n",
c.KeyID,
strings.ToUpper(method),
path,
gmtTime,
)
mac := hmac.New(sha256.New, []byte(c.SecretKey))
mac.Write([]byte(signingString))
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
authHeader := fmt.Sprintf(
`Signature keyId="%s",algorithm="hmac-sha256",headers="@request-target date",signature="%s"`,
c.KeyID, signature,
)
return map[string]string{
"Date": gmtTime,
"Authorization": authHeader,
}, nil
}
func (c *InfiniClient) Request(method, path string, payload interface{}) (map[string]interface{}, error) {
// Sign
headers, err := c.signRequest(method, path)
if err != nil {
return nil, err
}
// Encode JSON body if provided
var body io.Reader
if payload != nil {
b, err := json.Marshal(payload)
if err != nil {
return nil, err
}
body = bytes.NewBuffer(b)
headers["Content-Type"] = "application/json"
}
// Build request
req, err := http.NewRequest(method, c.BaseURL+path, body)
if err != nil {
return nil, err
}
// Set headers
for k, v := range headers {
req.Header.Set(k, v)
}
// Send
resp, err := c.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Check status
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("request failed: %s, body=%s", resp.Status, string(bodyBytes))
}
// Decode JSON
var data map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return nil, err
}
return data, nil
}请求头中的 Date 必须与服务器时间保持 ±300 秒以内误差;否则将返回:
401 Unauthorized请确保服务器与 NTP 同步。
Infini 向商户推送订单状态回调时,会附带签名。 商户需对签名进行校验,以确认消息来源可信并防止内容被篡改。
Webhook 请求包含以下 Header:
| Header | 说明 |
|---|---|
| X-Webhook-Timestamp | Unix 时间戳 |
| X-Webhook-Event-Id | 本次事件唯一 ID |
| X-Webhook-Signature | HMAC-SHA256 签名值 |
签名字符串格式:
{timestamp}.{event_id}.{payload_body}示例:
1700000000.1234.{"event":"order.completed", "order_id":"xxx"}计算方式:
expected_signature = HMAC_SHA256(webhook_secret, signing_content)判断合法性:
X-Webhook-Signature == expected_signature@app.route('/webhook', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Webhook-Signature')
timestamp = request.headers.get('X-Webhook-Timestamp')
event_id = request.headers.get('X-Webhook-Event-Id')
if not all([signature, timestamp, event_id]):
return {"error": "Missing required headers"}, 400
payload = request.get_data(as_text=True)
signed_content = f"{timestamp}.{event_id}.{payload}"
expected_sig = hmac.new(
WEBHOOK_SECRET.encode(),
signed_content.encode(),
hashlib.sha256
).hexdigest()
if expected_sig != signature:
return {"error": "Invalid signature"}, 401
# Process webhook payload
return {"status": "ok"}- 私钥(secret_key)仅展示一次,应立即安全备份。
- 不得将 Secret Key 暴露在网页、JS、App 或公共仓库中。
- 建议使用 KMS / Secret Manager 管理密钥。
- 可在创建密钥时启用 IP 白名单限制访问来源。
- Webhook 回调必须使用 HTTPS。
- 建议定期轮换密钥(Key Rotation)。