Skip to content

认证与签名 ​

租户 API 的每个请求都以 HMAC-SHA256 签名。服务器依序检查密钥、时间戳、nonce、签名、租户状态、IP 白名单与限流,全部通过才会处理请求。

请求头部 ​

头部内容
X-Api-Key密钥 ID:沙箱 ek_s_…、正式 ek_l_…,例如 ek_s_1a_XXXXXXXXXXXXXXXX
X-Timestamp目前的 Unix 时间,秒(9–11 位数字);与服务器时间差须在 ±300 秒内
X-Nonce每个请求都不同的随机字符串,16–64 个字母或数字;10 分钟内不可重复
X-Signature签名,64 个十六进制字符(建议小写;大小写都接受)
Content-Type有正文时为 application/json

密钥 ​

  • 密钥 ID(X-Api-Key)格式是 ek_<s|l>_<租户代号>_<16 码>:s 为沙箱、l 为正式。密钥 ID 本身就指出是哪个租户,所以请求不需要另外带公司代码。
  • Secret 是 es_ 加 40 个字母或数字。整个字符串(包含 es_)以 UTF-8 字节作为 HMAC 密钥。
  • 密钥在 Console 上线与对接 → API 密钥 管理:
    • 每个环境(沙箱、正式)最多 10 把有效密钥。
    • 「显示」「拷贝」Secret 需要先验证双因子(TOTP),每次都会留下审核记录。
    • 轮替:产生新密钥,旧密钥转为「轮替中」,24 小时后失效;请在期限内把新 Secret 部署到你的系统。
    • 禁用:立即失效,无法复原。
  • 沙箱密钥连到你的沙箱租户(测试币、模拟器桌),正式密钥连到正式租户。两者使用同一个主机与同一套 API。

签名算法 ​

text
string_to_sign = METHOD         + "\n" +
                 PATH_AND_QUERY + "\n" +
                 X-Timestamp    + "\n" +
                 X-Nonce        + "\n" +
                 hex(SHA-256(body))

X-Signature    = hex(HMAC-SHA256(key = secret, message = string_to_sign))
部分规则
METHOD大写的 HTTP 方法:GET 或 POST
PATH_AND_QUERY从 /api/tenant/v1/… 开始的完整路径,有查询字符串时加上 ? 与查询字符串。不含通信协定、主机与 # 之后的部分。查询字符串必须和实际送出的完全相同(参数顺序、百分比编码都一样)
X-Timestamp、X-Nonce与头部中的字符串完全相同
hex(SHA-256(body))实际送出的正文字节(UTF-8)的 SHA-256,小写十六进制。没有正文时是空字符串的哈希:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
换行只用 \n(LF),最后一行后面没有换行

GET 请求

服务器处理 GET 请求时一律忽略正文,正文哈希固定是空字符串的哈希。GET 的参数全部放在查询字符串,并且会被签进 PATH_AND_QUERY。

示例 ​

用下列(仅供示范的)密钥与输入计算:

项目值
密钥 IDek_s_1a_XXXXXXXXXXXXXXXX
Secretes_0123456789abcdefghijABCDEFGHIJ0123456789
方法与路径POST /api/tenant/v1/player/launch
X-Timestamp1790218800(2026-09-24T03:00:00Z)
X-Noncen0nce8H2kQ9xYz4LmP0aBcDe
正文{"username":"alice","lang":"ENG","device":"mobile"}
  1. 正文的 SHA-256:

    text
    a0c1cfba44962853266340b6610d542620041fae49e6bcb10e6446ca0f73ad6a
  2. 签名字符串(五行):

    text
    POST
    /api/tenant/v1/player/launch
    1790218800
    n0nce8H2kQ9xYz4LmP0aBcDe
    a0c1cfba44962853266340b6610d542620041fae49e6bcb10e6446ca0f73ad6a
  3. X-Signature:

    text
    6e4885c5d9f191b2e4ee5f4cb40ae148b13b277e606181bf853e040a06a0ac80

完整的请求:

http
POST /api/tenant/v1/player/launch HTTP/1.1
Host: elite.ewin888.com
X-Api-Key: ek_s_1a_XXXXXXXXXXXXXXXX
X-Timestamp: 1790218800
X-Nonce: n0nce8H2kQ9xYz4LmP0aBcDe
X-Signature: 6e4885c5d9f191b2e4ee5f4cb40ae148b13b277e606181bf853e040a06a0ac80
Content-Type: application/json

{"username":"alice","lang":"ENG","device":"mobile"}

GET 的示例:GET /api/tenant/v1/wallet/balance?username=alice,时间戳相同、nonce 为 n0nce8H2kQ9xYz4LmP0aBcDf、没有正文,签名为 273b761bda9ca095a8c7ae22908844bc1ce728212fceda3d0b2129fc9bb03032。

这两组数字可以直接贴到签名调试器验算。

代码示例 ​

每个示例都包含 sign 函数与一个送出请求的小工具,直接可执行文件案会印出上面示例的签名 6e4885c5…0ac80。

js
// elite tenant API: request signing (Node.js 18+, ES module, no dependencies)
import { createHash, createHmac, randomBytes } from 'node:crypto';
import { pathToFileURL } from 'node:url';

/**
 * X-Signature = hex(HMAC-SHA256(secret,
 *   METHOD + "\n" + PATH_AND_QUERY + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + hex(SHA-256(body))))
 */
export function sign(secret, method, pathAndQuery, timestamp, nonce, body = '') {
  const bodyHash = createHash('sha256').update(body, 'utf8').digest('hex');
  const stringToSign = [method.toUpperCase(), pathAndQuery, timestamp, nonce, bodyHash].join('\n');
  return createHmac('sha256', secret).update(stringToSign, 'utf8').digest('hex');
}

/** 32 hex characters; use a new nonce for every request, including retries */
export function newNonce() {
  return randomBytes(16).toString('hex');
}

/** Sends a signed request. Returns `data`, or throws { status, code, message }. */
export async function call(method, pathAndQuery, payload, { keyId, secret, baseUrl = 'https://elite.ewin-soft.com' }) {
  // Sign exactly the bytes you send: serialize once and reuse the string.
  const body = payload === undefined ? '' : JSON.stringify(payload);
  const timestamp = String(Math.floor(Date.now() / 1000)); // seconds, not milliseconds
  const nonce = newNonce();
  const headers = {
    'X-Api-Key': keyId,
    'X-Timestamp': timestamp,
    'X-Nonce': nonce,
    'X-Signature': sign(secret, method, pathAndQuery, timestamp, nonce, body),
  };
  if (body) headers['Content-Type'] = 'application/json';
  const res = await fetch(baseUrl + pathAndQuery, { method, headers, body: body || undefined, signal: AbortSignal.timeout(10_000) });
  const json = await res.json().catch(() => null);
  if (!res.ok || !json?.ok) {
    throw { status: res.status, code: json?.error?.code ?? `HTTP_${res.status}`, message: json?.error?.message ?? res.statusText };
  }
  return json.data;
}

// Example:
//   const auth = { keyId: process.env.ELITE_KEY_ID, secret: process.env.ELITE_SECRET };
//   const { url } = await call('POST', '/api/tenant/v1/player/launch', { username: 'alice' }, auth);
//   const { balance } = await call('GET', '/api/tenant/v1/wallet/balance?username=alice', undefined, auth);

// Worked example from the docs: prints 6e4885c5d9f191b2e4ee5f4cb40ae148b13b277e606181bf853e040a06a0ac80
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  console.log(
    sign(
      'es_0123456789abcdefghijABCDEFGHIJ0123456789',
      'POST',
      '/api/tenant/v1/player/launch',
      '1790218800',
      'n0nce8H2kQ9xYz4LmP0aBcDe',
      '{"username":"alice","lang":"ENG","device":"mobile"}',
    ),
  );
}
php
<?php
// elite tenant API: request signing (PHP 7.2+; the request helper needs ext-curl)

/**
 * hex(HMAC-SHA256(secret, METHOD \n PATH_AND_QUERY \n TIMESTAMP \n NONCE \n hex(SHA-256(body))))
 */
function elite_sign(string $secret, string $method, string $pathAndQuery, string $timestamp, string $nonce, string $body = ''): string
{
    $stringToSign = strtoupper($method) . "\n" . $pathAndQuery . "\n" . $timestamp . "\n" . $nonce . "\n" . hash('sha256', $body);
    return hash_hmac('sha256', $stringToSign, $secret);
}

/**
 * Sends a signed request and returns `data`. Throws RuntimeException with the error code on failure.
 */
function elite_call(string $method, string $pathAndQuery, ?array $payload, string $keyId, string $secret, string $baseUrl = 'https://elite.ewin-soft.com'): array
{
    // Sign exactly the bytes you send: encode once and reuse the string.
    $body = $payload === null ? '' : json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $timestamp = (string) time();         // seconds
    $nonce = bin2hex(random_bytes(16));   // new nonce for every request, including retries
    $headers = [
        'X-Api-Key: ' . $keyId,
        'X-Timestamp: ' . $timestamp,
        'X-Nonce: ' . $nonce,
        'X-Signature: ' . elite_sign($secret, $method, $pathAndQuery, $timestamp, $nonce, $body),
    ];
    if ($body !== '') {
        $headers[] = 'Content-Type: application/json';
    }
    $ch = curl_init($baseUrl . $pathAndQuery);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 10,
    ]);
    if ($body !== '') {
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    }
    $raw = curl_exec($ch);
    $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    $json = json_decode((string) $raw, true);
    if ($status < 200 || $status >= 300 || empty($json['ok'])) {
        $code = $json['error']['code'] ?? ('HTTP_' . $status);
        throw new RuntimeException($code . ': ' . ($json['error']['message'] ?? ''), $status);
    }
    return $json['data'];
}

// Example:
//   $url = elite_call('POST', '/api/tenant/v1/player/launch', ['username' => 'alice'], $keyId, $secret)['url'];
//   $balance = elite_call('GET', '/api/tenant/v1/wallet/balance?username=alice', null, $keyId, $secret)['balance'];

// Worked example from the docs: prints 6e4885c5d9f191b2e4ee5f4cb40ae148b13b277e606181bf853e040a06a0ac80
if (PHP_SAPI === 'cli' && isset($argv[0]) && realpath($argv[0]) === __FILE__) {
    echo elite_sign('es_0123456789abcdefghijABCDEFGHIJ0123456789', 'POST', '/api/tenant/v1/player/launch',
        '1790218800', 'n0nce8H2kQ9xYz4LmP0aBcDe', '{"username":"alice","lang":"ENG","device":"mobile"}'), PHP_EOL;
}
cs
// elite tenant API: request signing (.NET 6+)
using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

// Worked example from the docs: prints 6e4885c5d9f191b2e4ee5f4cb40ae148b13b277e606181bf853e040a06a0ac80
Console.WriteLine(EliteSigner.Sign("es_0123456789abcdefghijABCDEFGHIJ0123456789", "POST", "/api/tenant/v1/player/launch",
    "1790218800", "n0nce8H2kQ9xYz4LmP0aBcDe", "{\"username\":\"alice\",\"lang\":\"ENG\",\"device\":\"mobile\"}"));

public static class EliteSigner
{
    /// hex(HMAC-SHA256(secret, METHOD \n PATH_AND_QUERY \n TIMESTAMP \n NONCE \n hex(SHA-256(body))))
    public static string Sign(string secret, string method, string pathAndQuery, string timestamp, string nonce, string body)
    {
        var bodyHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(body))).ToLowerInvariant();
        var stringToSign = $"{method.ToUpperInvariant()}\n{pathAndQuery}\n{timestamp}\n{nonce}\n{bodyHash}";
        var mac = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes(stringToSign));
        return Convert.ToHexString(mac).ToLowerInvariant();
    }

    /// Sends a signed request and returns `data`. Throws HttpRequestException on failure.
    public static async Task<JsonElement> CallAsync(HttpClient http, string method, string pathAndQuery, object? payload,
        string keyId, string secret, string baseUrl = "https://elite.ewin-soft.com")
    {
        // Sign exactly the bytes you send: serialize once and reuse the string.
        var body = payload is null ? "" : JsonSerializer.Serialize(payload);
        var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();                 // seconds
        var nonce = Convert.ToHexString(RandomNumberGenerator.GetBytes(16)).ToLowerInvariant(); // new for every request
        using var request = new HttpRequestMessage(new HttpMethod(method), baseUrl + pathAndQuery);
        request.Headers.Add("X-Api-Key", keyId);
        request.Headers.Add("X-Timestamp", timestamp);
        request.Headers.Add("X-Nonce", nonce);
        request.Headers.Add("X-Signature", Sign(secret, method, pathAndQuery, timestamp, nonce, body));
        if (body.Length > 0) request.Content = new StringContent(body, Encoding.UTF8, "application/json");
        using var response = await http.SendAsync(request);
        var text = await response.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(text);
        var root = doc.RootElement;
        if (!response.IsSuccessStatusCode || !root.GetProperty("ok").GetBoolean())
        {
            var error = root.GetProperty("error");
            throw new HttpRequestException($"{error.GetProperty("code").GetString()}: {error.GetProperty("message").GetString()}");
        }
        return root.GetProperty("data").Clone();
    }
}
java
// elite tenant API: request signing (Java 11+, no dependencies)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.Locale;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public final class EliteSigner {
    private static final SecureRandom RANDOM = new SecureRandom();

    /** hex(HMAC-SHA256(secret, METHOD \n PATH_AND_QUERY \n TIMESTAMP \n NONCE \n hex(SHA-256(body)))) */
    public static String sign(String secret, String method, String pathAndQuery, String timestamp, String nonce, String body) throws Exception {
        String bodyHash = hex(MessageDigest.getInstance("SHA-256").digest(body.getBytes(StandardCharsets.UTF_8)));
        String stringToSign = method.toUpperCase(Locale.ROOT) + "\n" + pathAndQuery + "\n" + timestamp + "\n" + nonce + "\n" + bodyHash;
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        return hex(mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)));
    }

    /** 32 hex characters; use a new nonce for every request, including retries */
    public static String newNonce() {
        byte[] bytes = new byte[16];
        RANDOM.nextBytes(bytes);
        return hex(bytes);
    }

    /** Sends a signed request and returns the JSON response body; throws on a non-2xx status. */
    public static String call(HttpClient http, String method, String pathAndQuery, String jsonBody,
                              String keyId, String secret, String baseUrl) throws Exception {
        // Sign exactly the bytes you send: build the JSON string once and reuse it.
        String body = jsonBody == null ? "" : jsonBody;
        String timestamp = Long.toString(System.currentTimeMillis() / 1000); // seconds
        String nonce = newNonce();
        HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(baseUrl + pathAndQuery))
            .timeout(Duration.ofSeconds(10))
            .header("X-Api-Key", keyId)
            .header("X-Timestamp", timestamp)
            .header("X-Nonce", nonce)
            .header("X-Signature", sign(secret, method, pathAndQuery, timestamp, nonce, body));
        if (body.isEmpty()) {
            builder.method(method, HttpRequest.BodyPublishers.noBody());
        } else {
            builder.header("Content-Type", "application/json")
                .method(method, HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8));
        }
        HttpResponse<String> response = http.send(builder.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
        if (response.statusCode() / 100 != 2) {
            throw new RuntimeException("HTTP " + response.statusCode() + ": " + response.body());
        }
        return response.body();
    }

    private static String hex(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte b : bytes) {
            sb.append(Character.forDigit((b >> 4) & 0xf, 16)).append(Character.forDigit(b & 0xf, 16));
        }
        return sb.toString();
    }

    // Worked example from the docs: prints 6e4885c5d9f191b2e4ee5f4cb40ae148b13b277e606181bf853e040a06a0ac80
    public static void main(String[] args) throws Exception {
        System.out.println(sign("es_0123456789abcdefghijABCDEFGHIJ0123456789", "POST", "/api/tenant/v1/player/launch",
            "1790218800", "n0nce8H2kQ9xYz4LmP0aBcDe", "{\"username\":\"alice\",\"lang\":\"ENG\",\"device\":\"mobile\"}"));
    }
}
py
"""elite tenant API: request signing (Python 3.8+, standard library only)."""
import hashlib
import hmac
import json
import secrets
import time
import urllib.request


def sign(secret: str, method: str, path_and_query: str, timestamp: str, nonce: str, body: bytes = b"") -> str:
    """hex(HMAC-SHA256(secret, METHOD \\n PATH_AND_QUERY \\n TIMESTAMP \\n NONCE \\n hex(SHA-256(body))))"""
    body_hash = hashlib.sha256(body).hexdigest()
    string_to_sign = "\n".join([method.upper(), path_and_query, timestamp, nonce, body_hash])
    return hmac.new(secret.encode("utf-8"), string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()


def call(method, path_and_query, payload, key_id, secret, base_url="https://elite.ewin-soft.com"):
    """Sends a signed request and returns `data`. Raises urllib.error.HTTPError on 4xx/5xx."""
    # Sign exactly the bytes you send: serialize once and reuse them.
    body = b"" if payload is None else json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
    timestamp = str(int(time.time()))  # seconds
    nonce = secrets.token_hex(16)  # new nonce for every request, including retries
    headers = {
        "X-Api-Key": key_id,
        "X-Timestamp": timestamp,
        "X-Nonce": nonce,
        "X-Signature": sign(secret, method, path_and_query, timestamp, nonce, body),
        "User-Agent": "my-backend/1.0",
    }
    if body:
        headers["Content-Type"] = "application/json"
    request = urllib.request.Request(base_url + path_and_query, data=body or None, method=method, headers=headers)
    with urllib.request.urlopen(request, timeout=10) as response:
        return json.loads(response.read())["data"]


# Example:
#   url = call("POST", "/api/tenant/v1/player/launch", {"username": "alice"}, KEY_ID, SECRET)["url"]
#   balance = call("GET", "/api/tenant/v1/wallet/balance?username=alice", None, KEY_ID, SECRET)["balance"]

if __name__ == "__main__":
    # Worked example from the docs: prints 6e4885c5d9f191b2e4ee5f4cb40ae148b13b277e606181bf853e040a06a0ac80
    print(sign("es_0123456789abcdefghijABCDEFGHIJ0123456789", "POST", "/api/tenant/v1/player/launch",
               "1790218800", "n0nce8H2kQ9xYz4LmP0aBcDe", b'{"username":"alice","lang":"ENG","device":"mobile"}'))
go
// elite tenant API: request signing (Go 1.20+, standard library only)
package main

import (
	"bytes"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"
)

// Sign returns hex(HMAC-SHA256(secret, METHOD \n PATH_AND_QUERY \n TIMESTAMP \n NONCE \n hex(SHA-256(body)))).
func Sign(secret, method, pathAndQuery, timestamp, nonce string, body []byte) string {
	sum := sha256.Sum256(body)
	stringToSign := strings.Join([]string{strings.ToUpper(method), pathAndQuery, timestamp, nonce, hex.EncodeToString(sum[:])}, "\n")
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(stringToSign))
	return hex.EncodeToString(mac.Sum(nil))
}

// NewNonce returns 32 hex characters; use a new nonce for every request, including retries.
func NewNonce() string {
	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		panic(err)
	}
	return hex.EncodeToString(b)
}

// Call sends a signed request and returns the JSON response body; it returns an error on a non-2xx status.
// Sign exactly the bytes you send: marshal the JSON once and pass the same slice here.
func Call(client *http.Client, method, pathAndQuery string, body []byte, keyID, secret, baseURL string) ([]byte, error) {
	timestamp := strconv.FormatInt(time.Now().Unix(), 10) // seconds
	nonce := NewNonce()
	req, err := http.NewRequest(method, baseURL+pathAndQuery, bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("X-Api-Key", keyID)
	req.Header.Set("X-Timestamp", timestamp)
	req.Header.Set("X-Nonce", nonce)
	req.Header.Set("X-Signature", Sign(secret, method, pathAndQuery, timestamp, nonce, body))
	if len(body) > 0 {
		req.Header.Set("Content-Type", "application/json")
	}
	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	data, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}
	if res.StatusCode/100 != 2 {
		return data, fmt.Errorf("HTTP %d: %s", res.StatusCode, data)
	}
	return data, nil
}

// Worked example from the docs: prints 6e4885c5d9f191b2e4ee5f4cb40ae148b13b277e606181bf853e040a06a0ac80
func main() {
	fmt.Println(Sign("es_0123456789abcdefghijABCDEFGHIJ0123456789", "POST", "/api/tenant/v1/player/launch",
		"1790218800", "n0nce8H2kQ9xYz4LmP0aBcDe", []byte(`{"username":"alice","lang":"ENG","device":"mobile"}`)))
}

服务器的检查顺序 ​

顺序检查失败时
1X-Api-Key 的格式401 UNAUTHORIZED(invalid credentials)
2正文不超过 64 KB413 PAYLOAD_TOO_LARGE
3密钥存在、未禁用(轮替中的旧密钥在 24 小时内仍有效)401 UNAUTHORIZED(invalid credentials)
4X-Timestamp 格式正确、与服务器时间差 ≤ 300 秒401 UNAUTHORIZED(timestamp outside the ±300 s window)
5X-Nonce 格式(16–64 字母或数字)401 UNAUTHORIZED(invalid credentials)
6签名以固定时间比对401 UNAUTHORIZED(invalid credentials)
7租户未禁用、未停权403 TENANT_SUSPENDED
8来源 IP 在白名单内(有设置时)403 IP_NOT_ALLOWED
9租户限流429 RATE_LIMITED
10nonce 在 10 分钟内没有用过401 UNAUTHORIZED(nonce already used)
  • 密钥不存在与签名错误回同一个消息,避免被用来探测密钥。
  • nonce 只有在前面全部通过时才会被记录,所以因签名错误、限流等被拒绝的请求不会占用 nonce。即使如此,每次重试都请产生新的 nonce 与时间戳。
  • 收到 nonce already used 表示这个请求没有被处理(例如网络层自动重送了同一个请求)。

IP 白名单 ​

在 Console 上线与对接 → IP 白名单与 Webhook 设置,最多 100 笔 IPv4、IPv6 地址或 CIDR(例如 203.0.113.10、203.0.113.0/24、2001:db8::/32)。空白代表不限制。

  • 判断依据是 Cloudflare 看到的来源 IP(CF-Connecting-IP),也就是你的服务器的对外 IP。
  • 服务器同时有 IPv4 与 IPv6 时,请两种都加入,或让 HTTP 客户端固定使用其中一种。
  • 白名单设置在正式租户上;沙箱密钥不受限制。

常见错误 ​

状况原因与解法
一直收到 invalid credentials用签名调试器输入同样的值,逐行比对签名字符串
路径少了前缀PATH_AND_QUERY 必须从 /api/tenant/v1 开始,不是相对于 Base URL 的 /player/launch
GET 签名错误忘了把查询字符串签进去,或签名用的查询字符串与实际送出的编码、顺序不同
POST 签名错误签名用的正文和送出的正文不是同一个字符串:只串行化一次,签名与送出都用那个字符串;不要让 HTTP 函数库重新串行化
非英文字符以 UTF-8 字节计算正文哈希(例如中文暱称)
timestamp outside the ±300 s window服务器时钟偏移(请打开 NTP),或送了毫秒而不是秒
nonce already used重试时沿用了旧的 nonce;每个请求都要产生新的 nonce
Secret 不对Secret 要包含 es_ 前缀;密钥 ID 与 Secret 必须是同一把、同一个环境
IP_NOT_ALLOWED对外 IP 不在白名单,或服务器改走 IPv6

elite 租户集成 API v1